[clang] [compiler-rt] [llvm] [FlexFat] Modern Port of LowFat for Lean C/C++ Bounds Checking (PR #188893)

Kenneth Gao via llvm-commits llvm-commits at lists.llvm.org
Thu Mar 26 19:00:08 PDT 2026


https://github.com/duckyfuz created https://github.com/llvm/llvm-project/pull/188893

None

>From d92871e642d01eeece130221150770a6cb887250 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 20 Jan 2026 21:03:44 +0800
Subject: [PATCH 01/55] [LowFat] Add LowFatSanitizer pass skeleton with debug
 logging

---
 .../Instrumentation/LowFatSanitizer.h         | 30 +++++++++++++++++++
 llvm/lib/Passes/PassBuilder.cpp               | 11 +++++++
 llvm/lib/Passes/PassRegistry.def              |  4 +++
 .../Transforms/Instrumentation/CMakeLists.txt |  1 +
 .../Instrumentation/LowFatSanitizer.cpp       | 25 ++++++++++++++++
 5 files changed, 71 insertions(+)
 create mode 100644 llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
 create mode 100644 llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp

diff --git a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
new file mode 100644
index 0000000000000..b5688505cb799
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
@@ -0,0 +1,30 @@
+//===- LowFatSanitizer.h - LowFat Pointer Bounds Checking -------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_TRANSFORMS_INSTRUMENTATION_LOWFATSANITIZER_H
+#define LLVM_TRANSFORMS_INSTRUMENTATION_LOWFATSANITIZER_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+class Module;
+
+struct LowFatSanitizerOptions {
+  // LowFat currently does not support any options.
+};
+
+class LowFatSanitizerPass : public PassInfoMixin<LowFatSanitizerPass> {
+public:
+  LLVM_ABI
+  LowFatSanitizerPass(const LowFatSanitizerOptions &Options);
+  LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+  static bool isRequired() { return true; }
+};
+
+} // namespace llvm
+
+#endif // LLVM_TRANSFORMS_INSTRUMENTATION_LOWFATSANITIZER_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 8bb78c8c7df63..b57112a71ddb0 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -247,6 +247,7 @@
 #include "llvm/Transforms/Instrumentation/DataFlowSanitizer.h"
 #include "llvm/Transforms/Instrumentation/GCOVProfiler.h"
 #include "llvm/Transforms/Instrumentation/HWAddressSanitizer.h"
+#include "llvm/Transforms/Instrumentation/LowFatSanitizer.h"
 #include "llvm/Transforms/Instrumentation/InstrProfiling.h"
 #include "llvm/Transforms/Instrumentation/KCFI.h"
 #include "llvm/Transforms/Instrumentation/LowerAllowCheckPass.h"
@@ -978,6 +979,16 @@ Expected<HWAddressSanitizerOptions> parseHWASanPassOptions(StringRef Params) {
   return Result;
 }
 
+Expected<LowFatSanitizerOptions> parseLowFatPassOptions(StringRef Params) {
+  LowFatSanitizerOptions Result;
+  if (!Params.empty()) {
+    return make_error<StringError>(
+        formatv("invalid LowFatSanitizer pass parameter '{}'", Params).str(),
+        inconvertibleErrorCode());
+  }
+  return Result;
+}
+
 Expected<EmbedBitcodeOptions> parseEmbedBitcodePassOptions(StringRef Params) {
   EmbedBitcodeOptions Result;
   while (!Params.empty()) {
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 2cfb5b2592601..791dfbce17165 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -216,6 +216,10 @@ MODULE_PASS_WITH_PARAMS(
     "hwasan", "HWAddressSanitizerPass",
     [](HWAddressSanitizerOptions Opts) { return HWAddressSanitizerPass(Opts); },
     parseHWASanPassOptions, "kernel;recover")
+MODULE_PASS_WITH_PARAMS(
+    "lowfat", "LowFatSanitizerPass",
+    [](LowFatSanitizerOptions Opts) { return LowFatSanitizerPass(Opts); },
+    parseLowFatPassOptions, "")
 MODULE_PASS_WITH_PARAMS(
     "internalize", "InternalizePass",
     [](SmallVector<std::string, 0> PreservedGVs) {
diff --git a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
index 80576c61fd80c..36fef5c50ddd0 100644
--- a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
+++ b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
@@ -14,6 +14,7 @@ add_llvm_component_library(LLVMInstrumentation
   IndirectCallPromotion.cpp
   InstrProfiling.cpp
   KCFI.cpp
+  LowFatSanitizer.cpp
   LowerAllowCheckPass.cpp
   PGOCtxProfFlattening.cpp
   PGOCtxProfLowering.cpp
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
new file mode 100644
index 0000000000000..913c4d33d37b6
--- /dev/null
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -0,0 +1,25 @@
+//===- LowFatSanitizer.cpp - LowFat Pointer Bounds Checking ---------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Instrumentation/LowFatSanitizer.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/Debug.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "lowfat"
+
+LowFatSanitizerPass::LowFatSanitizerPass(const LowFatSanitizerOptions &Options) {}
+
+PreservedAnalyses LowFatSanitizerPass::run(Module &M,
+                                           ModuleAnalysisManager &AM) {
+  LLVM_DEBUG(dbgs() << "[LowFat] Running on module: " << M.getName() << "\n");
+
+  return PreservedAnalyses::all();
+}

>From bc1546a58a0b45192e22662276730fc8fa8baf79 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 21 Jan 2026 23:25:30 +0800
Subject: [PATCH 02/55] [LowFat] Initialize LowFatSanitizerPass with empty
 options

---
 llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h | 3 +++
 llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp        | 3 ++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
index b5688505cb799..9a39b532a296d 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
@@ -23,6 +23,9 @@ class LowFatSanitizerPass : public PassInfoMixin<LowFatSanitizerPass> {
   LowFatSanitizerPass(const LowFatSanitizerOptions &Options);
   LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
   static bool isRequired() { return true; }
+
+private:
+  LowFatSanitizerOptions Options;
 };
 
 } // namespace llvm
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 913c4d33d37b6..b31ae562a5f51 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -15,7 +15,8 @@ using namespace llvm;
 
 #define DEBUG_TYPE "lowfat"
 
-LowFatSanitizerPass::LowFatSanitizerPass(const LowFatSanitizerOptions &Options) {}
+LowFatSanitizerPass::LowFatSanitizerPass(const LowFatSanitizerOptions &Options)
+    : Options(Options) {}
 
 PreservedAnalyses LowFatSanitizerPass::run(Module &M,
                                            ModuleAnalysisManager &AM) {

>From 5a3e08e48161be63d027d894197880ea52b7ef10 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 21 Jan 2026 23:41:48 +0800
Subject: [PATCH 03/55] [LowFat] Create stub runtime lib

---
 compiler-rt/lib/lowfat/CMakeLists.txt  |  32 ++++++++
 compiler-rt/lib/lowfat/lf_interface.h  |  48 +++++++++++
 compiler-rt/lib/lowfat/lf_rtl.cpp      | 109 +++++++++++++++++++++++++
 compiler-rt/test/lowfat/CMakeLists.txt |  11 +++
 4 files changed, 200 insertions(+)
 create mode 100644 compiler-rt/lib/lowfat/CMakeLists.txt
 create mode 100644 compiler-rt/lib/lowfat/lf_interface.h
 create mode 100644 compiler-rt/lib/lowfat/lf_rtl.cpp
 create mode 100644 compiler-rt/test/lowfat/CMakeLists.txt

diff --git a/compiler-rt/lib/lowfat/CMakeLists.txt b/compiler-rt/lib/lowfat/CMakeLists.txt
new file mode 100644
index 0000000000000..398e343d82c6d
--- /dev/null
+++ b/compiler-rt/lib/lowfat/CMakeLists.txt
@@ -0,0 +1,32 @@
+include_directories(..)
+
+set(LOWFAT_SOURCES
+  lf_rtl.cpp
+)
+
+set(LOWFAT_HEADERS
+  lf_interface.h
+)
+
+set(LOWFAT_CFLAGS ${SANITIZER_COMMON_CFLAGS})
+append_rtti_flag(OFF LOWFAT_CFLAGS)
+set(LOWFAT_COMMON_DEFINITIONS)
+
+# Static runtime library.
+add_compiler_rt_component(lowfat)
+
+if(COMPILER_RT_HAS_LOWFAT)
+  foreach(arch ${LOWFAT_SUPPORTED_ARCH})
+    add_compiler_rt_runtime(clang_rt.lowfat
+      STATIC
+      ARCHS ${arch}
+      SOURCES ${LOWFAT_SOURCES}
+      ADDITIONAL_HEADERS ${LOWFAT_HEADERS}
+      OBJECT_LIBS RTSanitizerCommon
+                  RTSanitizerCommonLibc
+                  RTSanitizerCommonSymbolizer
+      CFLAGS ${LOWFAT_CFLAGS}
+      DEFS ${LOWFAT_COMMON_DEFINITIONS}
+      PARENT_TARGET lowfat)
+  endforeach()
+endif()
diff --git a/compiler-rt/lib/lowfat/lf_interface.h b/compiler-rt/lib/lowfat/lf_interface.h
new file mode 100644
index 0000000000000..7e6e90c9cf0e0
--- /dev/null
+++ b/compiler-rt/lib/lowfat/lf_interface.h
@@ -0,0 +1,48 @@
+//===-- lf_interface.h - LowFat Sanitizer Runtime Interface ----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file declares the LowFat Sanitizer runtime interface functions.
+// The runtime library must define these functions so the instrumented program
+// can call them.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LF_INTERFACE_H
+#define LF_INTERFACE_H
+
+#include "sanitizer_common/sanitizer_internal_defs.h"
+
+using __sanitizer::uptr;
+
+extern "C" {
+
+// Initialize the LowFat Sanitizer runtime. Called early during program startup.
+SANITIZER_INTERFACE_ATTRIBUTE void __lf_init();
+
+// Perform a bounds check on a pointer access.
+// ptr: The pointer being accessed
+// size: The size of the access in bytes
+SANITIZER_INTERFACE_ATTRIBUTE void __lf_check_bounds(uptr ptr, uptr size);
+
+// Report an out-of-bounds error.
+// ptr: The pointer that caused the violation
+// base: The base address of the allocation
+// bound: The size of the allocation
+SANITIZER_INTERFACE_ATTRIBUTE void __lf_report_oob(uptr ptr, uptr base,
+                                                    uptr bound);
+
+// Get the base address of an allocation from a pointer.
+// Returns the base address, or 0 if the pointer is not within a LowFat region.
+SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_base(uptr ptr);
+
+// Get the size (bound) of an allocation from a pointer.
+// Returns the allocation size, or 0 if the pointer is not within a LowFat region.
+SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_size(uptr ptr);
+
+}  // extern "C"
+
+#endif  // LF_INTERFACE_H
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
new file mode 100644
index 0000000000000..66cbbaa9ad2ee
--- /dev/null
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -0,0 +1,109 @@
+//===-- lf_rtl.cpp - LowFat Sanitizer Runtime Library ---------------------===//
+//
+// 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 is the main file of the LowFat Sanitizer runtime library.
+//
+// LowFat pointers encode allocation bounds information directly in the pointer
+// value through careful memory layout. This allows O(1) bounds checking without
+// maintaining separate metadata.
+//
+//===----------------------------------------------------------------------===//
+
+#include "lf_interface.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+using namespace __sanitizer;
+
+namespace __lowfat {
+
+// Flag to track initialization state
+static bool lowfat_inited = false;
+
+// TODO: Add LowFat-specific configuration tables
+// These will define the memory regions and size classes for LowFat allocations
+
+static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound) {
+  Printf("=================================================================\n");
+  Printf("==ERROR: LowFat: out-of-bounds access detected\n");
+  Printf("  pointer: 0x%zx\n", ptr);
+  Printf("  base:    0x%zx\n", base);
+  Printf("  bound:   %zu bytes\n", bound);
+  Printf("=================================================================\n");
+
+  // Print stack trace
+  BufferedStackTrace stack;
+  stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr,
+               common_flags()->fast_unwind_on_fatal);
+  stack.Print();
+
+  Die();
+}
+
+}  // namespace __lowfat
+
+// ---------------------- Interface Functions ----------------------
+
+extern "C" {
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __lf_init() {
+  if (__lowfat::lowfat_inited)
+    return;
+
+  // Initialize sanitizer common
+  // InitializeCommonFlags() would go here if we had custom flags
+
+  Printf("LowFat Sanitizer: runtime initialized\n");
+
+  __lowfat::lowfat_inited = true;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __lf_check_bounds(uptr ptr, uptr size) {
+  // TODO: Implement actual bounds checking
+  // For now, this is a stub that does nothing
+  //
+  // The full implementation should:
+  // 1. Extract the region index from the pointer's high bits
+  // 2. Look up the allocation size for that region
+  // 3. Compute the base address using the region's alignment
+  // 4. Check if ptr + size <= base + allocation_size
+  (void)ptr;
+  (void)size;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __lf_report_oob(uptr ptr, uptr base, uptr bound) {
+  __lowfat::PrintErrorAndDie(ptr, base, bound);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __lf_get_base(uptr ptr) {
+  // TODO: Implement base address extraction
+  // This will use the LowFat memory layout to compute the base
+  (void)ptr;
+  return 0;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __lf_get_size(uptr ptr) {
+  // TODO: Implement size extraction
+  // This will look up the size class from the pointer's region
+  (void)ptr;
+  return 0;
+}
+
+}  // extern "C"
+
+// Ensure initialization runs early via .preinit_array on ELF platforms
+#if SANITIZER_CAN_USE_PREINIT_ARRAY
+__attribute__((section(".preinit_array"), used)) static auto preinit =
+    __lf_init;
+#endif
\ No newline at end of file
diff --git a/compiler-rt/test/lowfat/CMakeLists.txt b/compiler-rt/test/lowfat/CMakeLists.txt
new file mode 100644
index 0000000000000..651b543e51a02
--- /dev/null
+++ b/compiler-rt/test/lowfat/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(LOWFAT_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(LOWFAT_LIT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
+
+set(LOWFAT_TESTSUITES)
+set(LOWFAT_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
+
+if(COMPILER_RT_HAS_LOWFAT)
+  list(APPEND LOWFAT_TEST_DEPS lowfat)
+endif()
+
+# TODO: add tests for lowfat

>From 47a57ccf0df9cb3be3918a52c4544967e2fe28cb Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 21 Jan 2026 23:45:51 +0800
Subject: [PATCH 04/55] [LowFat] Add lowfat to clang and allow link with LowFat
 runtime library

---
 clang/include/clang/Basic/Sanitizers.def            |  3 +++
 clang/lib/CodeGen/BackendUtil.cpp                   |  6 ++++++
 clang/lib/Driver/SanitizerArgs.cpp                  |  3 +++
 clang/lib/Driver/ToolChains/Darwin.cpp              |  3 +++
 clang/lib/Driver/ToolChains/Linux.cpp               |  2 ++
 .../cmake/Modules/AllSupportedArchDefs.cmake        |  1 +
 compiler-rt/cmake/config-ix.cmake                   | 13 ++++++++++++-
 7 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def
index da85431625026..ab7da16c6b68f 100644
--- a/clang/include/clang/Basic/Sanitizers.def
+++ b/clang/include/clang/Basic/Sanitizers.def
@@ -142,6 +142,9 @@ SANITIZER("kcfi", KCFI)
 // Safe Stack
 SANITIZER("safe-stack", SafeStack)
 
+// LowFat Pointer Bounds Checking
+SANITIZER("lowfat", LowFat)
+
 // Shadow Call Stack
 SANITIZER("shadow-call-stack", ShadowCallStack)
 
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index d411ef1bf8763..55025c43448f2 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -85,6 +85,7 @@
 #include "llvm/Transforms/Instrumentation/SanitizerCoverage.h"
 #include "llvm/Transforms/Instrumentation/ThreadSanitizer.h"
 #include "llvm/Transforms/Instrumentation/TypeSanitizer.h"
+#include "llvm/Transforms/Instrumentation/LowFatSanitizer.h"
 #include "llvm/Transforms/ObjCARC.h"
 #include "llvm/Transforms/Scalar/EarlyCSE.h"
 #include "llvm/Transforms/Scalar/GVN.h"
@@ -768,6 +769,11 @@ static void addSanitizers(const Triple &TargetTriple,
       MPM.addPass(DataFlowSanitizerPass(LangOpts.NoSanitizeFiles,
                                         PB.getVirtualFileSystemPtr()));
     }
+
+    if (LangOpts.Sanitize.has(SanitizerKind::LowFat)) {
+      LowFatSanitizerOptions Opts;
+      MPM.addPass(LowFatSanitizerPass(Opts));
+    }
   };
   if (ClSanitizeOnOptimizerEarlyEP) {
     PB.registerOptimizerEarlyEPCallback(
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index be068b2381d06..8d59955b29515 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -663,6 +663,9 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
                      SanitizerKind::Address | SanitizerKind::HWAddress |
                          SanitizerKind::KernelAddress |
                          SanitizerKind::KernelHWAddress |
+                         SanitizerKind::Memory),
+      std::make_pair(SanitizerKind::LowFat,
+                     SanitizerKind::Address | SanitizerKind::HWAddress |
                          SanitizerKind::Memory)};
 
   // Enable toolchain specific default sanitizers if not explicitly disabled.
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index fb75739360328..c4239c3cedd9b 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -3815,6 +3815,9 @@ SanitizerMask Darwin::getSupportedSanitizers() const {
   if (IsX86_64)
     Res |= SanitizerKind::NumericalStability;
 
+  if (IsX86_64 || IsAArch64)
+    Res |= SanitizerKind::LowFat;
+
   return Res;
 }
 
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index cdbf21fb90263..39c9868e07d3f 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -937,6 +937,8 @@ SanitizerMask Linux::getSupportedSanitizers() const {
   }
   if (IsX86_64)
     Res |= SanitizerKind::NumericalStability;
+  if (IsX86_64 || IsAArch64)
+    Res |= SanitizerKind::LowFat;
   if (!IsAndroid)
     Res |= SanitizerKind::Memory;
 
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 0cae5da26c3e7..0787e2fa61bfb 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -121,6 +121,7 @@ set(ALL_XRAY_SUPPORTED_ARCH ${X86_64} ${ARM32} ${ARM64} ${MIPS32} ${MIPS64}
 endif()
 set(ALL_XRAY_DSO_SUPPORTED_ARCH ${X86_64} ${ARM64})
 set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64})
+set(ALL_LOWFAT_SUPPORTED_ARCH ${X86_64} ${ARM64})
 
 if (UNIX)
   if (OS_NAME MATCHES "Linux")
diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake
index 1f82ff3cf7531..90b12c7f84d0e 100644
--- a/compiler-rt/cmake/config-ix.cmake
+++ b/compiler-rt/cmake/config-ix.cmake
@@ -689,6 +689,9 @@ if(APPLE)
   list_intersect(SHADOWCALLSTACK_SUPPORTED_ARCH
     ALL_SHADOWCALLSTACK_SUPPORTED_ARCH
     SANITIZER_COMMON_SUPPORTED_ARCH)
+  list_intersect(LOWFAT_SUPPORTED_ARCH
+    ALL_LOWFAT_SUPPORTED_ARCH
+    SANITIZER_COMMON_SUPPORTED_ARCH)
   list_intersect(ORC_SUPPORTED_ARCH
     ALL_ORC_SUPPORTED_ARCH
     SANITIZER_COMMON_SUPPORTED_ARCH)
@@ -724,6 +727,7 @@ else()
   filter_available_targets(XRAY_DSO_SUPPORTED_ARCH ${ALL_XRAY_DSO_SUPPORTED_ARCH})
   filter_available_targets(SHADOWCALLSTACK_SUPPORTED_ARCH
     ${ALL_SHADOWCALLSTACK_SUPPORTED_ARCH})
+  filter_available_targets(LOWFAT_SUPPORTED_ARCH ${ALL_LOWFAT_SUPPORTED_ARCH})
   filter_available_targets(GWP_ASAN_SUPPORTED_ARCH ${ALL_GWP_ASAN_SUPPORTED_ARCH})
   filter_available_targets(NSAN_SUPPORTED_ARCH ${ALL_NSAN_SUPPORTED_ARCH})
   filter_available_targets(ORC_SUPPORTED_ARCH ${ALL_ORC_SUPPORTED_ARCH})
@@ -760,7 +764,7 @@ if(COMPILER_RT_SUPPORTED_ARCH)
 endif()
 message(STATUS "Compiler-RT supported architectures: ${COMPILER_RT_SUPPORTED_ARCH}")
 
-set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi)
+set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi;lowfat)
 set(COMPILER_RT_SANITIZERS_TO_BUILD all CACHE STRING
     "sanitizers to build if supported on the target (all;${ALL_SANITIZERS})")
 list_replace(COMPILER_RT_SANITIZERS_TO_BUILD all "${ALL_SANITIZERS}")
@@ -902,6 +906,13 @@ else()
   set(COMPILER_RT_HAS_SAFESTACK FALSE)
 endif()
 
+if (COMPILER_RT_HAS_SANITIZER_COMMON AND LOWFAT_SUPPORTED_ARCH AND
+    OS_NAME MATCHES "Darwin|Linux")
+  set(COMPILER_RT_HAS_LOWFAT TRUE)
+else()
+  set(COMPILER_RT_HAS_LOWFAT FALSE)
+endif()
+
 if (COMPILER_RT_HAS_SANITIZER_COMMON AND CFI_SUPPORTED_ARCH)
   set(COMPILER_RT_HAS_CFI TRUE)
 else()

>From 513eda0a777d3c2ca60720427b1027b15b99f4d5 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 21 Jan 2026 23:58:05 +0800
Subject: [PATCH 05/55] [LowFat] Add -fsanitize=lowfat linker support for Clang

---
 clang/include/clang/Driver/SanitizerArgs.h | 1 +
 clang/lib/Driver/ToolChains/CommonArgs.cpp | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 84fb66e16bee3..82af822d55171 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -119,6 +119,7 @@ class SanitizerArgs {
     return Sanitizers.has(SanitizerKind::NumericalStability);
   }
   bool needsRtsanRt() const { return Sanitizers.has(SanitizerKind::Realtime); }
+  bool needsLowFatRt() const { return Sanitizers.has(SanitizerKind::LowFat); }
 
   bool hasMemTag() const {
     return hasMemtagHeap() || hasMemtagStack() || hasMemtagGlobals();
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 10a1a412eea08..d20108de6a309 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1718,6 +1718,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
     if (SanArgs.linkCXXRuntimes())
       StaticRuntimes.push_back("scudo_standalone_cxx");
   }
+  if (SanArgs.needsLowFatRt())
+    StaticRuntimes.push_back("lowfat");
 }
 
 // Should be called before we add system libraries (C++ ABI, libstdc++/libc++,

>From 2b585b118db66767ecbc40613574fdd3379c3d37 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 31 Jan 2026 23:35:10 +0800
Subject: [PATCH 06/55] [LowFat] Add sanitizer infrastructure

---
 compiler-rt/lib/lowfat/lf_rtl.cpp             |  18 +-
 .../Instrumentation/LowFatSanitizer.cpp       | 172 +++++++++++++++++-
 2 files changed, 187 insertions(+), 3 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 66cbbaa9ad2ee..fb5b60427f041 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -48,6 +48,17 @@ static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound) {
 
 }  // namespace __lowfat
 
+namespace __sanitizer {
+void BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, void *context,
+                                    bool request_fast, u32 max_depth) {
+  uptr top = 0;
+  uptr bottom = 0;
+  GetThreadStackTopAndBottom(false, &top, &bottom);
+  bool fast = StackTrace::WillUseFastUnwind(request_fast);
+  Unwind(max_depth, pc, bp, context, top, bottom, fast);
+}
+}  // namespace __sanitizer
+
 // ---------------------- Interface Functions ----------------------
 
 extern "C" {
@@ -102,8 +113,13 @@ uptr __lf_get_size(uptr ptr) {
 
 }  // extern "C"
 
-// Ensure initialization runs early via .preinit_array on ELF platforms
 #if SANITIZER_CAN_USE_PREINIT_ARRAY
+// ELF platforms: use .preinit_array for earliest possible initialization
 __attribute__((section(".preinit_array"), used)) static auto preinit =
     __lf_init;
+#else
+// macOS/other platforms: use constructor attribute
+__attribute__((constructor)) static void lowfat_constructor() {
+  __lf_init();
+}
 #endif
\ No newline at end of file
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index b31ae562a5f51..0b57458fff0d2 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -5,22 +5,190 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
+//
+// This file implements the LowFat Sanitizer instrumentation pass.
+//
+// LowFat pointers encode allocation bounds information directly in the pointer
+// value through careful memory layout. This pass instruments memory accesses
+// to call runtime functions that verify bounds using this encoded information.
+//
+//===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/Instrumentation/LowFatSanitizer.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Function.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/Type.h"
 #include "llvm/Support/Debug.h"
 
 using namespace llvm;
 
 #define DEBUG_TYPE "lowfat"
 
+STATISTIC(NumInstrumentedLoads, "Number of loads instrumented");
+STATISTIC(NumInstrumentedStores, "Number of stores instrumented");
+STATISTIC(NumInstrumentedAtomics, "Number of atomic operations instrumented");
+
+namespace {
+
+/// Helper class to instrument a module with LowFat bounds checks.
+class LowFatSanitizer {
+public:
+  LowFatSanitizer(Module &M, const LowFatSanitizerOptions &Options)
+      : M(M), Options(Options), DL(M.getDataLayout()),
+        IntptrTy(DL.getIntPtrType(M.getContext())) {}
+
+  bool run();
+
+private:
+  /// Get or create the declaration for __lf_check_bounds.
+  FunctionCallee getCheckBoundsFn();
+
+  /// Instrument a single function.
+  bool instrumentFunction(Function &F);
+
+  /// Instrument a memory access instruction.
+  /// Returns true if instrumentation was inserted.
+  bool instrumentMemoryAccess(Instruction *I, Value *Ptr, Type *AccessTy);
+
+  Module &M;
+  const LowFatSanitizerOptions &Options;
+  const DataLayout &DL;
+  Type *IntptrTy;
+  FunctionCallee CheckBoundsFn;
+};
+
+FunctionCallee LowFatSanitizer::getCheckBoundsFn() {
+  if (!CheckBoundsFn) {
+    // void __lf_check_bounds(uptr ptr, uptr size)
+    Type *VoidTy = Type::getVoidTy(M.getContext());
+    CheckBoundsFn = M.getOrInsertFunction(
+        "__lf_check_bounds", FunctionType::get(VoidTy, {IntptrTy, IntptrTy}, false));
+  }
+  return CheckBoundsFn;
+}
+
+bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
+                                              Type *AccessTy) {
+  // Skip if the access type size is not known at compile time
+  TypeSize AccessSize = DL.getTypeStoreSize(AccessTy);
+  if (AccessSize.isScalable()) {
+    LLVM_DEBUG(dbgs() << "[LowFat] Skipping scalable type access\n");
+    return false;
+  }
+
+  IRBuilder<> IRB(I);
+
+  // Convert pointer to integer
+  Value *PtrInt = IRB.CreatePtrToInt(Ptr, IntptrTy);
+
+  // Create size constant
+  Value *SizeVal = ConstantInt::get(IntptrTy, AccessSize.getFixedValue());
+
+  // Insert call to __lf_check_bounds(ptr, size)
+  IRB.CreateCall(getCheckBoundsFn(), {PtrInt, SizeVal});
+
+  LLVM_DEBUG(dbgs() << "[LowFat] Instrumented: " << *I << "\n");
+  return true;
+}
+
+bool LowFatSanitizer::instrumentFunction(Function &F) {
+  // Skip functions that shouldn't be instrumented
+  if (F.isDeclaration())
+    return false;
+
+  // Skip the runtime library functions themselves
+  if (F.getName().starts_with("__lf_"))
+    return false;
+
+  // Skip functions with nosanitize attribute
+  if (F.hasFnAttribute(Attribute::NoSanitizeBounds))
+    return false;
+
+  LLVM_DEBUG(dbgs() << "[LowFat] Instrumenting function: " << F.getName() << "\n");
+
+  bool Modified = false;
+
+  // Collect instructions to instrument first to avoid iterator invalidation
+  SmallVector<std::pair<Instruction *, std::pair<Value *, Type *>>, 16> ToInstrument;
+
+  for (Instruction &I : instructions(F)) {
+    Value *Ptr = nullptr;
+    Type *AccessTy = nullptr;
+
+    if (auto *LI = dyn_cast<LoadInst>(&I)) {
+      if (!LI->isVolatile()) {
+        Ptr = LI->getPointerOperand();
+        AccessTy = LI->getType();
+      }
+    } else if (auto *SI = dyn_cast<StoreInst>(&I)) {
+      if (!SI->isVolatile()) {
+        Ptr = SI->getPointerOperand();
+        AccessTy = SI->getValueOperand()->getType();
+      }
+    } else if (auto *AI = dyn_cast<AtomicRMWInst>(&I)) {
+      if (!AI->isVolatile()) {
+        Ptr = AI->getPointerOperand();
+        AccessTy = AI->getValOperand()->getType();
+      }
+    } else if (auto *AI = dyn_cast<AtomicCmpXchgInst>(&I)) {
+      if (!AI->isVolatile()) {
+        Ptr = AI->getPointerOperand();
+        AccessTy = AI->getCompareOperand()->getType();
+      }
+    }
+
+    if (Ptr && AccessTy) {
+      ToInstrument.push_back({&I, {Ptr, AccessTy}});
+    }
+  }
+
+  // Now instrument collected instructions
+  for (auto &Entry : ToInstrument) {
+    Instruction *I = Entry.first;
+    Value *Ptr = Entry.second.first;
+    Type *AccessTy = Entry.second.second;
+
+    if (instrumentMemoryAccess(I, Ptr, AccessTy)) {
+      Modified = true;
+      if (isa<LoadInst>(I))
+        ++NumInstrumentedLoads;
+      else if (isa<StoreInst>(I))
+        ++NumInstrumentedStores;
+      else
+        ++NumInstrumentedAtomics;
+    }
+  }
+
+  return Modified;
+}
+
+bool LowFatSanitizer::run() {
+  LLVM_DEBUG(dbgs() << "[LowFat] Running on module: " << M.getName() << "\n");
+
+  bool Modified = false;
+
+  for (Function &F : M) {
+    Modified |= instrumentFunction(F);
+  }
+
+  return Modified;
+}
+
+} // anonymous namespace
+
 LowFatSanitizerPass::LowFatSanitizerPass(const LowFatSanitizerOptions &Options)
     : Options(Options) {}
 
 PreservedAnalyses LowFatSanitizerPass::run(Module &M,
                                            ModuleAnalysisManager &AM) {
-  LLVM_DEBUG(dbgs() << "[LowFat] Running on module: " << M.getName() << "\n");
+  LowFatSanitizer Sanitizer(M, Options);
+  if (!Sanitizer.run())
+    return PreservedAnalyses::all();
 
-  return PreservedAnalyses::all();
+  return PreservedAnalyses::none();
 }

>From 19b978436a6341351a302e08523b137370f42c07 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 31 Jan 2026 23:39:58 +0800
Subject: [PATCH 07/55] [LowFat] Resolve linker issues on Darwin

---
 clang/lib/Driver/ToolChains/Darwin.cpp |  3 +++
 compiler-rt/lib/lowfat/CMakeLists.txt  | 23 ++++++++++++++++++++---
 2 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index c4239c3cedd9b..abf3d67ada664 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -1621,6 +1621,9 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args,
       AddLinkRuntimeLib(Args, CmdArgs, "stats_client", RLO_AlwaysLink);
       AddLinkSanitizerLibArgs(Args, CmdArgs, "stats");
     }
+    if (Sanitize.needsLowFatRt()) {
+      AddLinkSanitizerLibArgs(Args, CmdArgs, "lowfat");
+    }
   }
 
   if (Sanitize.needsMemProfRt())
diff --git a/compiler-rt/lib/lowfat/CMakeLists.txt b/compiler-rt/lib/lowfat/CMakeLists.txt
index 398e343d82c6d..905bd66b41b88 100644
--- a/compiler-rt/lib/lowfat/CMakeLists.txt
+++ b/compiler-rt/lib/lowfat/CMakeLists.txt
@@ -16,17 +16,34 @@ set(LOWFAT_COMMON_DEFINITIONS)
 add_compiler_rt_component(lowfat)
 
 if(COMPILER_RT_HAS_LOWFAT)
-  foreach(arch ${LOWFAT_SUPPORTED_ARCH})
+  if(APPLE)
+    add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS)
+
+    add_compiler_rt_runtime(clang_rt.lowfat
+      SHARED
+      OS ${SANITIZER_COMMON_SUPPORTED_OS}
+      ARCHS ${LOWFAT_SUPPORTED_ARCH}
+      SOURCES ${LOWFAT_SOURCES}
+      ADDITIONAL_HEADERS ${LOWFAT_HEADERS}
+      OBJECT_LIBS RTSanitizerCommon
+                  RTSanitizerCommonLibc
+                  RTSanitizerCommonSymbolizer
+      CFLAGS ${LOWFAT_CFLAGS}
+      LINK_FLAGS ${WEAK_SYMBOL_LINK_FLAGS}
+      DEFS ${LOWFAT_COMMON_DEFINITIONS}
+      PARENT_TARGET lowfat)
+  else()
     add_compiler_rt_runtime(clang_rt.lowfat
       STATIC
-      ARCHS ${arch}
+      ARCHS ${LOWFAT_SUPPORTED_ARCH}
       SOURCES ${LOWFAT_SOURCES}
       ADDITIONAL_HEADERS ${LOWFAT_HEADERS}
       OBJECT_LIBS RTSanitizerCommon
                   RTSanitizerCommonLibc
                   RTSanitizerCommonSymbolizer
+                  RTSanitizerCommonSymbolizerInternal
       CFLAGS ${LOWFAT_CFLAGS}
       DEFS ${LOWFAT_COMMON_DEFINITIONS}
       PARENT_TARGET lowfat)
-  endforeach()
+  endif()
 endif()

>From f61ee705c0f61da5d959a0b32363654fbceba584 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 10 Feb 2026 15:52:15 +0800
Subject: [PATCH 08/55] [LowFat] Implement memory regions and bound check
 functions

---
 compiler-rt/lib/lowfat/CMakeLists.txt |   1 +
 compiler-rt/lib/lowfat/lf_config.h    | 152 ++++++++++++++++++++++++++
 compiler-rt/lib/lowfat/lf_rtl.cpp     |  87 +++++++++++----
 3 files changed, 217 insertions(+), 23 deletions(-)
 create mode 100644 compiler-rt/lib/lowfat/lf_config.h

diff --git a/compiler-rt/lib/lowfat/CMakeLists.txt b/compiler-rt/lib/lowfat/CMakeLists.txt
index 905bd66b41b88..f23d99d59a43a 100644
--- a/compiler-rt/lib/lowfat/CMakeLists.txt
+++ b/compiler-rt/lib/lowfat/CMakeLists.txt
@@ -5,6 +5,7 @@ set(LOWFAT_SOURCES
 )
 
 set(LOWFAT_HEADERS
+  lf_config.h
   lf_interface.h
 )
 
diff --git a/compiler-rt/lib/lowfat/lf_config.h b/compiler-rt/lib/lowfat/lf_config.h
new file mode 100644
index 0000000000000..02deb4375fff3
--- /dev/null
+++ b/compiler-rt/lib/lowfat/lf_config.h
@@ -0,0 +1,152 @@
+//===-- lf_config.h - LowFat Memory Layout Configuration -----------------===//
+//
+// 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 defines the LowFat memory layout configuration.
+//
+// LowFat pointers encode allocation bounds directly in the pointer value:
+// - Memory is divided into regions, each for a specific size class
+// - Within each region, allocations are aligned to their size class
+// - Given a pointer, the base can be computed by masking off low bits
+// - The size can be looked up from a table using the region index
+//
+// Memory Layout (64-bit, example):
+//   Region 0: [0x10_0000_0000, 0x20_0000_0000) - 16-byte allocations
+//   Region 1: [0x20_0000_0000, 0x30_0000_0000) - 32-byte allocations
+//   Region 2: [0x30_0000_0000, 0x40_0000_0000) - 64-byte allocations
+//   ...
+//   Region N: [0xN0_0000_0000, ...)            - 2^(N+4)-byte allocations
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LF_CONFIG_H
+#define LF_CONFIG_H
+
+#include "sanitizer_common/sanitizer_internal_defs.h"
+
+namespace __lowfat {
+
+using namespace __sanitizer;
+
+//===----------------------------------------------------------------------===//
+// Size Class Configuration
+//===----------------------------------------------------------------------===//
+
+// Minimum allocation size (must be power of 2)
+constexpr uptr kMinSizeLog = 4;  // 16 bytes
+constexpr uptr kMinSize = 1ULL << kMinSizeLog;
+
+// Maximum allocation size (must be power of 2)
+constexpr uptr kMaxSizeLog = 30;  // 1 GB
+constexpr uptr kMaxSize = 1ULL << kMaxSizeLog;
+
+// Number of size classes (one per power of 2)
+constexpr uptr kNumSizeClasses = kMaxSizeLog - kMinSizeLog + 1;
+
+// Size class index for a given size (rounded up to next power of 2)
+// Returns 0 for sizes <= 16, 1 for sizes 17-32, etc.
+inline uptr SizeClassIndex(uptr size) {
+  if (size <= kMinSize)
+    return 0;
+  // Count leading zeros to find the highest set bit
+  uptr log2 = (sizeof(uptr) * 8 - 1) - __builtin_clzll(size);
+  // Round up if not exact power of 2
+  if (size > (1ULL << log2))
+    log2++;
+  return log2 - kMinSizeLog;
+}
+
+// Get the allocation size for a size class
+inline uptr SizeClassToSize(uptr class_index) {
+  return 1ULL << (class_index + kMinSizeLog);
+}
+
+//===----------------------------------------------------------------------===//
+// Memory Region Configuration
+//===----------------------------------------------------------------------===//
+
+// Each region is 4GB (32 bits of address space per region)
+constexpr uptr kRegionSizeLog = 32;
+constexpr uptr kRegionSize = 1ULL << kRegionSizeLog;
+
+// Base address where LowFat regions start
+// We use the upper portion of the address space
+// On 64-bit systems: 0x100000000000 (17.6 TB mark)
+constexpr uptr kRegionBase = 0x100000000000ULL;
+
+// Get the region number from a pointer
+inline uptr GetRegionIndex(uptr ptr) {
+  if (ptr < kRegionBase)
+    return (uptr)-1;  // Not a LowFat pointer
+  return (ptr - kRegionBase) >> kRegionSizeLog;
+}
+
+// Get the start address of a region
+inline uptr GetRegionStart(uptr region_index) {
+  return kRegionBase + (region_index << kRegionSizeLog);
+}
+
+// Check if a pointer is within LowFat managed memory
+inline bool IsLowFatPointer(uptr ptr) {
+  uptr region = GetRegionIndex(ptr);
+  return region < kNumSizeClasses;
+}
+
+//===----------------------------------------------------------------------===//
+// Bounds Computation
+//===----------------------------------------------------------------------===//
+
+// Get the base address of an allocation from a LowFat pointer
+// This uses the key LowFat insight: allocations are aligned to their size
+inline uptr GetBase(uptr ptr) {
+  uptr region = GetRegionIndex(ptr);
+  if (region >= kNumSizeClasses)
+    return 0;  // Not a valid LowFat pointer
+  
+  uptr size = SizeClassToSize(region);
+  uptr mask = ~(size - 1);  // Mask off low bits
+  return ptr & mask;
+}
+
+// Get the allocation size from a LowFat pointer
+inline uptr GetSize(uptr ptr) {
+  uptr region = GetRegionIndex(ptr);
+  if (region >= kNumSizeClasses)
+    return 0;  // Not a valid LowFat pointer
+  return SizeClassToSize(region);
+}
+
+// Check if ptr..ptr+access_size is within bounds
+inline bool CheckBounds(uptr ptr, uptr access_size) {
+  uptr region = GetRegionIndex(ptr);
+  if (region >= kNumSizeClasses)
+    return true;  // Not a LowFat pointer, assume valid (or could error)
+  
+  uptr alloc_size = SizeClassToSize(region);
+  uptr base = ptr & ~(alloc_size - 1);
+  uptr end = base + alloc_size;
+  
+  return (ptr + access_size) <= end;
+}
+
+//===----------------------------------------------------------------------===//
+// Region Table (for lookup by region index)
+//===----------------------------------------------------------------------===//
+
+struct RegionInfo {
+  uptr size;           // Allocation size for this region
+  uptr alignment;      // Alignment (same as size for LowFat)
+  uptr mask;           // Mask to get base address: ptr & mask
+};
+
+// This table is indexed by region number
+// Initialized in lf_rtl.cpp
+extern RegionInfo kRegions[kNumSizeClasses];
+
+}  // namespace __lowfat
+
+#endif  // LF_CONFIG_H
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index fb5b60427f041..ee772ee196d1d 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -15,6 +15,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "lf_interface.h"
+#include "lf_config.h"
 #include "sanitizer_common/sanitizer_common.h"
 #include "sanitizer_common/sanitizer_flags.h"
 #include "sanitizer_common/sanitizer_stacktrace.h"
@@ -26,8 +27,49 @@ namespace __lowfat {
 // Flag to track initialization state
 static bool lowfat_inited = false;
 
-// TODO: Add LowFat-specific configuration tables
-// These will define the memory regions and size classes for LowFat allocations
+// Region table - initialized in __lf_init
+// TODO: not actually needed, to use for convenience
+RegionInfo kRegions[kNumSizeClasses];
+
+// Pointers to the start of each mapped region
+static uptr region_bases[kNumSizeClasses];
+
+// Free list heads for each region (simple bump allocator for now)
+static uptr region_next_alloc[kNumSizeClasses];
+
+static void InitRegionTable() {
+  for (uptr i = 0; i < kNumSizeClasses; i++) {
+    uptr size = SizeClassToSize(i);
+    kRegions[i].size = size;
+    kRegions[i].alignment = size;
+    kRegions[i].mask = ~(size - 1);
+  }
+}
+
+// Initialize memory regions using mmap
+// Each region is mapped at a fixed address for the corresponding size class
+static bool InitMemoryRegions() {
+  for (uptr i = 0; i < kNumSizeClasses; i++) {
+    uptr region_start = GetRegionStart(i);
+    
+    // Reserve the region without committing physical memory
+    // MmapFixedNoReserve maps memory but doesn't allocate physical pages
+    // until they're accessed (lazy allocation)
+    bool success = MmapFixedNoReserve(region_start, kRegionSize, "lowfat_region");
+    
+    if (!success) {
+      Printf("LowFat: Failed to map region %zu at 0x%zx\n", i, region_start);
+      return false;
+    }
+    
+    region_bases[i] = region_start;
+    region_next_alloc[i] = region_start;
+  }
+  
+  Printf("LowFat: Mapped %zu regions starting at 0x%zx\n", 
+         kNumSizeClasses, kRegionBase);
+  return true;
+}
 
 static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound) {
   Printf("=================================================================\n");
@@ -68,26 +110,31 @@ void __lf_init() {
   if (__lowfat::lowfat_inited)
     return;
 
-  // Initialize sanitizer common
-  // InitializeCommonFlags() would go here if we had custom flags
+  Printf("LowFat Sanitizer: initializing runtime\n");
+
+  __lowfat::InitRegionTable();
+  
+  if (!__lowfat::InitMemoryRegions()) {
+    Printf("LowFat Sanitizer: failed to initialize memory regions\n");
+    Die();
+  }
 
-  Printf("LowFat Sanitizer: runtime initialized\n");
+  Printf("LowFat Sanitizer: initialized runtime\n");
 
   __lowfat::lowfat_inited = true;
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
 void __lf_check_bounds(uptr ptr, uptr size) {
-  // TODO: Implement actual bounds checking
-  // For now, this is a stub that does nothing
-  //
-  // The full implementation should:
-  // 1. Extract the region index from the pointer's high bits
-  // 2. Look up the allocation size for that region
-  // 3. Compute the base address using the region's alignment
-  // 4. Check if ptr + size <= base + allocation_size
-  (void)ptr;
-  (void)size;
+  if (!__lowfat::IsLowFatPointer(ptr)) { // Not a LowFat-managed pointer, skip check
+    return;
+  }
+
+  if (!__lowfat::CheckBounds(ptr, size)) {
+    uptr base = __lowfat::GetBase(ptr);
+    uptr alloc_size = __lowfat::GetSize(ptr);
+    __lowfat::PrintErrorAndDie(ptr, base, alloc_size);
+  }
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
@@ -97,18 +144,12 @@ void __lf_report_oob(uptr ptr, uptr base, uptr bound) {
 
 SANITIZER_INTERFACE_ATTRIBUTE
 uptr __lf_get_base(uptr ptr) {
-  // TODO: Implement base address extraction
-  // This will use the LowFat memory layout to compute the base
-  (void)ptr;
-  return 0;
+  return __lowfat::GetBase(ptr);
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
 uptr __lf_get_size(uptr ptr) {
-  // TODO: Implement size extraction
-  // This will look up the size class from the pointer's region
-  (void)ptr;
-  return 0;
+  return __lowfat::GetSize(ptr);
 }
 
 }  // extern "C"

>From 3fbb1582d803af432adefcd8971c1591673adbfb Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 11 Feb 2026 10:37:33 +0800
Subject: [PATCH 09/55] [LowFat] Implement memory alloc and dealloc with free
 list

---
 compiler-rt/lib/lowfat/lf_rtl.cpp | 113 ++++++++++++++++++++++++++----
 1 file changed, 101 insertions(+), 12 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index ee772ee196d1d..3451ad7599f52 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -34,15 +34,23 @@ RegionInfo kRegions[kNumSizeClasses];
 // Pointers to the start of each mapped region
 static uptr region_bases[kNumSizeClasses];
 
-// Free list heads for each region (simple bump allocator for now)
+// Bump pointer: next fresh address to allocate from in each region
 static uptr region_next_alloc[kNumSizeClasses];
 
+// Segregated free lists: one singly-linked list per size class
+// Free blocks store a pointer to the next free block at their start
+struct FreeBlock {
+  FreeBlock *next;
+};
+static FreeBlock *free_lists[kNumSizeClasses];
+
 static void InitRegionTable() {
   for (uptr i = 0; i < kNumSizeClasses; i++) {
     uptr size = SizeClassToSize(i);
     kRegions[i].size = size;
     kRegions[i].alignment = size;
     kRegions[i].mask = ~(size - 1);
+    free_lists[i] = nullptr;
   }
 }
 
@@ -58,7 +66,7 @@ static bool InitMemoryRegions() {
     bool success = MmapFixedNoReserve(region_start, kRegionSize, "lowfat_region");
     
     if (!success) {
-      Printf("LowFat: Failed to map region %zu at 0x%zx\n", i, region_start);
+      // Printf("LowFat: Failed to map region %zu at 0x%zx\n", i, region_start);
       return false;
     }
     
@@ -66,11 +74,75 @@ static bool InitMemoryRegions() {
     region_next_alloc[i] = region_start;
   }
   
-  Printf("LowFat: Mapped %zu regions starting at 0x%zx\n", 
-         kNumSizeClasses, kRegionBase);
+  // Printf("LowFat: Mapped %zu regions starting at 0x%zx\n", 
+  //        kNumSizeClasses, kRegionBase);
   return true;
 }
 
+// Allocate from a LowFat region
+// First checks the free list, then falls back to bump allocation
+static void *Allocate(uptr size) {
+  // Printf("LowFat: allocating %zu bytes\n", size);
+  if (size == 0)
+    size = 1;
+  
+  uptr class_index = SizeClassIndex(size);
+  if (class_index >= kNumSizeClasses) {
+    // Printf("LowFat: allocation size %zu exceeds max size class\n", size);
+    return nullptr;
+  }
+  
+  uptr alloc_size = SizeClassToSize(class_index);
+  
+  // 1. Try free list first
+  FreeBlock *block = free_lists[class_index];
+  if (block) {
+    free_lists[class_index] = block->next;
+    // Zero the memory (free list pointer was stored here)
+    internal_memset(block, 0, alloc_size);
+    return (void *)block;
+  }
+  
+  // 2. Fall back to bump allocation
+  uptr region_end = GetRegionStart(class_index) + kRegionSize;
+  uptr addr = region_next_alloc[class_index];
+  
+  // Ensure alignment (should already be aligned)
+  addr = (addr + alloc_size - 1) & ~(alloc_size - 1);
+  
+  if (addr + alloc_size > region_end) {
+    // Printf("LowFat: region %zu exhausted\n", class_index);
+    return nullptr;
+  }
+  
+  region_next_alloc[class_index] = addr + alloc_size;
+  return (void *)addr;
+}
+
+// Free a LowFat allocation by adding it to the free list
+static void Deallocate(void *ptr) {
+  if (!ptr)
+    return;
+  
+  uptr addr = (uptr)ptr;
+  // Printf("LowFat: freeing ptr 0x%zx\n", addr);
+  
+  // Validate this is a LowFat pointer
+  if (!IsLowFatPointer(addr)) {
+    // Printf("LowFat: __lf_free called on non-LowFat pointer 0x%zx\n", addr);
+    return;
+  }
+  
+  uptr region = GetRegionIndex(addr);
+  // Printf("LowFat: freed region %zu (size class %zu bytes)\n", 
+  //        region, SizeClassToSize(region));
+  
+  // Add to the head of the free list for this size class
+  FreeBlock *block = (FreeBlock *)ptr;
+  block->next = free_lists[region];
+  free_lists[region] = block;
+}
+
 static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound) {
   Printf("=================================================================\n");
   Printf("==ERROR: LowFat: out-of-bounds access detected\n");
@@ -110,29 +182,36 @@ void __lf_init() {
   if (__lowfat::lowfat_inited)
     return;
 
-  Printf("LowFat Sanitizer: initializing runtime\n");
+  // Printf("LowFat Sanitizer: initializing runtime\n");
 
   __lowfat::InitRegionTable();
   
   if (!__lowfat::InitMemoryRegions()) {
-    Printf("LowFat Sanitizer: failed to initialize memory regions\n");
+    // Printf("LowFat Sanitizer: failed to initialize memory regions\n");
     Die();
   }
 
-  Printf("LowFat Sanitizer: initialized runtime\n");
+  // Printf("LowFat Sanitizer: initialized runtime\n");
 
   __lowfat::lowfat_inited = true;
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
 void __lf_check_bounds(uptr ptr, uptr size) {
-  if (!__lowfat::IsLowFatPointer(ptr)) { // Not a LowFat-managed pointer, skip check
+  // Printf("LowFat: check_bounds(ptr=0x%zx, size=%zu)", ptr, size);
+  if (!__lowfat::IsLowFatPointer(ptr)) {
+    // Printf(" → not LowFat, skipping\n");
     return;
   }
-
-  if (!__lowfat::CheckBounds(ptr, size)) {
-    uptr base = __lowfat::GetBase(ptr);
-    uptr alloc_size = __lowfat::GetSize(ptr);
+  
+  uptr base = __lowfat::GetBase(ptr);
+  uptr alloc_size = __lowfat::GetSize(ptr);
+  bool in_bounds = __lowfat::CheckBounds(ptr, size);
+  // Printf(" → base=0x%zx, alloc=%zu, end=0x%zx, %s\n",
+  //        base, alloc_size, base + alloc_size,
+  //        in_bounds ? "OK" : "OOB!");
+
+  if (!in_bounds) {
     __lowfat::PrintErrorAndDie(ptr, base, alloc_size);
   }
 }
@@ -152,6 +231,16 @@ uptr __lf_get_size(uptr ptr) {
   return __lowfat::GetSize(ptr);
 }
 
+SANITIZER_INTERFACE_ATTRIBUTE
+void *__lf_malloc(uptr size) {
+  return __lowfat::Allocate(size);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __lf_free(void *ptr) {
+  __lowfat::Deallocate(ptr);
+}
+
 }  // extern "C"
 
 #if SANITIZER_CAN_USE_PREINIT_ARRAY

>From ba4039155a84d4490ea9ed26429f2107aaec346f Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 11 Feb 2026 10:45:26 +0800
Subject: [PATCH 10/55] [LowFat] Add transform pass basic test

---
 .../Instrumentation/LowFatSanitizer/basic.ll  | 92 +++++++++++++++++++
 1 file changed, 92 insertions(+)
 create mode 100644 llvm/test/Instrumentation/LowFatSanitizer/basic.ll

diff --git a/llvm/test/Instrumentation/LowFatSanitizer/basic.ll b/llvm/test/Instrumentation/LowFatSanitizer/basic.ll
new file mode 100644
index 0000000000000..cbb4aa1046121
--- /dev/null
+++ b/llvm/test/Instrumentation/LowFatSanitizer/basic.ll
@@ -0,0 +1,92 @@
+; RUN: opt < %s -passes=lowfat -S | FileCheck %s
+target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
+
+; Test 1: Load should be instrumented with __lf_check_bounds
+define i32 @test_load(ptr %p) {
+; CHECK-LABEL: @test_load
+; CHECK: %[[PTR:.*]] = ptrtoint ptr %p to i64
+; CHECK-NEXT: call void @__lf_check_bounds(i64 %[[PTR]], i64 4)
+; CHECK-NEXT: %val = load i32, ptr %p
+  %val = load i32, ptr %p, align 4
+  ret i32 %val
+}
+
+; Test 2: Store should be instrumented
+define void @test_store(ptr %p, i32 %v) {
+; CHECK-LABEL: @test_store
+; CHECK: %[[PTR:.*]] = ptrtoint ptr %p to i64
+; CHECK-NEXT: call void @__lf_check_bounds(i64 %[[PTR]], i64 4)
+; CHECK-NEXT: store i32 %v, ptr %p
+  store i32 %v, ptr %p, align 4
+  ret void
+}
+
+; Test 3: Volatile accesses should NOT be instrumented
+define i32 @test_volatile_load(ptr %p) {
+; CHECK-LABEL: @test_volatile_load
+; CHECK-NOT: call void @__lf_check_bounds
+; CHECK: load volatile i32, ptr %p
+  %val = load volatile i32, ptr %p, align 4
+  ret i32 %val
+}
+
+define void @test_volatile_store(ptr %p, i32 %v) {
+; CHECK-LABEL: @test_volatile_store
+; CHECK-NOT: call void @__lf_check_bounds
+; CHECK: store volatile i32 %v, ptr %p
+  store volatile i32 %v, ptr %p, align 4
+  ret void
+}
+
+; Test 4: Runtime functions (__lf_*) should NOT be instrumented
+define void @__lf_check_bounds(i64 %ptr, i64 %size) {
+; CHECK-LABEL: @__lf_check_bounds
+; CHECK-NOT: call void @__lf_check_bounds
+  ret void
+}
+
+; Test 5: i8 load should use size 1
+define i8 @test_load_i8(ptr %p) {
+; CHECK-LABEL: @test_load_i8
+; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 1)
+  %val = load i8, ptr %p, align 1
+  ret i8 %val
+}
+
+; Test 6: i64 store should use size 8
+define void @test_store_i64(ptr %p, i64 %v) {
+; CHECK-LABEL: @test_store_i64
+; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 8)
+  store i64 %v, ptr %p, align 8
+  ret void
+}
+
+; Test 7: AtomicRMW should be instrumented
+define i32 @test_atomic_rmw(ptr %p) {
+; CHECK-LABEL: @test_atomic_rmw
+; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 4)
+; CHECK: atomicrmw add ptr %p, i32 1
+  %old = atomicrmw add ptr %p, i32 1 monotonic
+  ret i32 %old
+}
+
+; Test 8: AtomicCmpXchg should be instrumented
+define { i32, i1 } @test_atomic_cmpxchg(ptr %p) {
+; CHECK-LABEL: @test_atomic_cmpxchg
+; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 4)
+; CHECK: cmpxchg ptr %p, i32 0, i32 1
+  %val = cmpxchg ptr %p, i32 0, i32 1 monotonic monotonic
+  ret { i32, i1 } %val
+}
+
+; Test 9: Multiple accesses in one function
+define void @test_multiple(ptr %p, ptr %q) {
+; CHECK-LABEL: @test_multiple
+; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 4)
+; CHECK: load i32
+; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 4)
+; CHECK: store i32
+  %val = load i32, ptr %p, align 4
+  store i32 %val, ptr %q, align 4
+  ret void
+}

>From e89c30bee11afdbfb0a4bde632afea26422a1bc6 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 11 Feb 2026 10:49:01 +0800
Subject: [PATCH 11/55] [LowFat] Add basic testcases for lf_rtl

---
 compiler-rt/test/lowfat/basic_inbounds.cpp    | 23 ++++++++++++++++
 .../test/lowfat/cross_boundary_oob.cpp        | 27 +++++++++++++++++++
 compiler-rt/test/lowfat/free_list_reuse.cpp   | 25 +++++++++++++++++
 3 files changed, 75 insertions(+)
 create mode 100644 compiler-rt/test/lowfat/basic_inbounds.cpp
 create mode 100644 compiler-rt/test/lowfat/cross_boundary_oob.cpp
 create mode 100644 compiler-rt/test/lowfat/free_list_reuse.cpp

diff --git a/compiler-rt/test/lowfat/basic_inbounds.cpp b/compiler-rt/test/lowfat/basic_inbounds.cpp
new file mode 100644
index 0000000000000..05e6290614e12
--- /dev/null
+++ b/compiler-rt/test/lowfat/basic_inbounds.cpp
@@ -0,0 +1,23 @@
+// RUN: %clangxx_lowfat %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s
+
+// Verify that the LowFat runtime initializes and basic in-bounds
+// allocations work without errors.
+
+extern "C" void *__lf_malloc(unsigned long size);
+extern "C" void __lf_free(void *ptr);
+
+int main() {
+  // CHECK: LowFat Sanitizer: initialized runtime
+  int *arr = (int *)__lf_malloc(10 * sizeof(int));
+  if (!arr)
+    return 1;
+
+  // In-bounds accesses — should not trigger OOB
+  arr[0] = 42;
+  arr[9] = 99;
+
+  __lf_free(arr);
+  // CHECK-NOT: ERROR: LowFat
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/cross_boundary_oob.cpp b/compiler-rt/test/lowfat/cross_boundary_oob.cpp
new file mode 100644
index 0000000000000..57dfa1434a032
--- /dev/null
+++ b/compiler-rt/test/lowfat/cross_boundary_oob.cpp
@@ -0,0 +1,27 @@
+// RUN: %clangxx_lowfat %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// Verify that a cross-boundary out-of-bounds access is detected.
+// Writing 8 bytes starting at offset 12 of a 16-byte slot crosses
+// the slot boundary (bytes 12-19 > 16 bytes).
+
+extern "C" void *__lf_malloc(unsigned long size);
+
+int main() {
+  // Allocate exactly 16 bytes → 16-byte size class (smallest slot)
+  char *buf = (char *)__lf_malloc(16);
+  if (!buf)
+    return 1;
+
+  // In-bounds write
+  buf[0] = 'A';
+  buf[15] = 'B';
+
+  // Cross-boundary OOB: write 8 bytes at offset 12
+  // ptr + 8 = buf+20 > buf+16 → out of bounds
+  // CHECK: ERROR: LowFat: out-of-bounds access detected
+  double *cross = (double *)(buf + 12);
+  *cross = 3.14;
+
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/free_list_reuse.cpp b/compiler-rt/test/lowfat/free_list_reuse.cpp
new file mode 100644
index 0000000000000..6460277ed5f81
--- /dev/null
+++ b/compiler-rt/test/lowfat/free_list_reuse.cpp
@@ -0,0 +1,25 @@
+// RUN: %clangxx_lowfat %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s
+
+// Verify that allocation and free list reuse works correctly.
+
+extern "C" void *__lf_malloc(unsigned long size);
+extern "C" void __lf_free(void *ptr);
+
+int main() {
+  // CHECK: LowFat Sanitizer: initialized runtime
+
+  // Allocate and free, then allocate again — should reuse from free list
+  int *a = (int *)__lf_malloc(10 * sizeof(int));
+  a[0] = 1;
+  __lf_free(a);
+
+  int *b = (int *)__lf_malloc(10 * sizeof(int));
+  // After free list reuse, b should equal a (same address reused)
+  b[0] = 2;
+  b[9] = 3;
+  __lf_free(b);
+
+  // CHECK-NOT: ERROR: LowFat
+  return 0;
+}

>From a4f469c40c2959d467b1613b6c79eac677554df6 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Fri, 13 Feb 2026 12:42:47 +0800
Subject: [PATCH 12/55] [LowFat] Implement malloc/free interceptors

---
 compiler-rt/lib/lowfat/CMakeLists.txt      |   8 +-
 compiler-rt/lib/lowfat/lf_allocator.h      |  33 ++++
 compiler-rt/lib/lowfat/lf_interceptors.cpp | 177 +++++++++++++++++++++
 compiler-rt/lib/lowfat/lf_rtl.cpp          |  11 +-
 4 files changed, 223 insertions(+), 6 deletions(-)
 create mode 100644 compiler-rt/lib/lowfat/lf_allocator.h
 create mode 100644 compiler-rt/lib/lowfat/lf_interceptors.cpp

diff --git a/compiler-rt/lib/lowfat/CMakeLists.txt b/compiler-rt/lib/lowfat/CMakeLists.txt
index f23d99d59a43a..22fa2c5463c1e 100644
--- a/compiler-rt/lib/lowfat/CMakeLists.txt
+++ b/compiler-rt/lib/lowfat/CMakeLists.txt
@@ -2,9 +2,11 @@ include_directories(..)
 
 set(LOWFAT_SOURCES
   lf_rtl.cpp
+  lf_interceptors.cpp
 )
 
 set(LOWFAT_HEADERS
+  lf_allocator.h
   lf_config.h
   lf_interface.h
 )
@@ -26,7 +28,8 @@ if(COMPILER_RT_HAS_LOWFAT)
       ARCHS ${LOWFAT_SUPPORTED_ARCH}
       SOURCES ${LOWFAT_SOURCES}
       ADDITIONAL_HEADERS ${LOWFAT_HEADERS}
-      OBJECT_LIBS RTSanitizerCommon
+      OBJECT_LIBS RTInterception
+                  RTSanitizerCommon
                   RTSanitizerCommonLibc
                   RTSanitizerCommonSymbolizer
       CFLAGS ${LOWFAT_CFLAGS}
@@ -39,7 +42,8 @@ if(COMPILER_RT_HAS_LOWFAT)
       ARCHS ${LOWFAT_SUPPORTED_ARCH}
       SOURCES ${LOWFAT_SOURCES}
       ADDITIONAL_HEADERS ${LOWFAT_HEADERS}
-      OBJECT_LIBS RTSanitizerCommon
+      OBJECT_LIBS RTInterception
+                  RTSanitizerCommon
                   RTSanitizerCommonLibc
                   RTSanitizerCommonSymbolizer
                   RTSanitizerCommonSymbolizerInternal
diff --git a/compiler-rt/lib/lowfat/lf_allocator.h b/compiler-rt/lib/lowfat/lf_allocator.h
new file mode 100644
index 0000000000000..698b98045567a
--- /dev/null
+++ b/compiler-rt/lib/lowfat/lf_allocator.h
@@ -0,0 +1,33 @@
+//===-- lf_allocator.h - LowFat Allocator Internal Interface ----*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Internal allocator interface shared between lf_rtl.cpp and
+// lf_interceptors.cpp.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LF_ALLOCATOR_H
+#define LF_ALLOCATOR_H
+
+#include "sanitizer_common/sanitizer_internal_defs.h"
+
+namespace __lowfat {
+
+using __sanitizer::uptr;
+
+// Allocate from a LowFat region. Returns nullptr if size exceeds max.
+void *Allocate(uptr size);
+
+// Free a LowFat allocation.
+void Deallocate(void *ptr);
+
+// Initialize interceptors (called from __lf_init).
+void InitializeInterceptors();
+
+} // namespace __lowfat
+
+#endif // LF_ALLOCATOR_H
diff --git a/compiler-rt/lib/lowfat/lf_interceptors.cpp b/compiler-rt/lib/lowfat/lf_interceptors.cpp
new file mode 100644
index 0000000000000..8d96b8d88ef0b
--- /dev/null
+++ b/compiler-rt/lib/lowfat/lf_interceptors.cpp
@@ -0,0 +1,177 @@
+//===-- lf_interceptors.cpp - LowFat Malloc/Free Interceptors -------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Interceptors for malloc/free/calloc/realloc that route heap allocations
+// through the LowFat allocator so all heap memory gets bounds-checked.
+//
+//===----------------------------------------------------------------------===//
+
+#include "interception/interception.h"
+#include "lf_allocator.h"
+#include "lf_config.h"
+#include "sanitizer_common/sanitizer_allocator_dlsym.h"
+
+using namespace __sanitizer;
+
+namespace __lowfat {
+extern bool lowfat_inited;
+} // namespace __lowfat
+
+// DlsymAlloc handles allocations that happen before our runtime is initialized
+// (e.g., during dynamic linker symbol resolution). Uses a small static buffer.
+namespace {
+struct DlsymAlloc : public DlSymAllocator<DlsymAlloc> {
+  static bool UseImpl() { return !__lowfat::lowfat_inited; }
+};
+} // namespace
+
+// Helper: should this allocation go through LowFat?
+// Allocations larger than our max size class fall back to system malloc.
+static inline bool ShouldUseLowFat(uptr size) {
+  return __lowfat::lowfat_inited && size > 0 && size <= __lowfat::kMaxSize;
+}
+
+//===----------------------------------------------------------------------===//
+// Interceptors
+//===----------------------------------------------------------------------===//
+
+INTERCEPTOR(void *, malloc, uptr size) {
+  if (DlsymAlloc::Use())
+    return DlsymAlloc::Allocate(size);
+  if (ShouldUseLowFat(size))
+    return __lowfat::Allocate(size);
+  return REAL(malloc)(size);
+}
+
+INTERCEPTOR(void, free, void *ptr) {
+  if (!ptr)
+    return;
+  if (DlsymAlloc::PointerIsMine(ptr))
+    return DlsymAlloc::Free(ptr);
+  if (__lowfat::IsLowFatPointer((uptr)ptr)) {
+    __lowfat::Deallocate(ptr);
+    return;
+  }
+  REAL(free)(ptr);
+}
+
+INTERCEPTOR(void *, calloc, uptr nmemb, uptr size) {
+  if (DlsymAlloc::Use())
+    return DlsymAlloc::Callocate(nmemb, size);
+  uptr total = nmemb * size;
+  if (ShouldUseLowFat(total)) {
+    void *ptr = __lowfat::Allocate(total);
+    if (ptr)
+      internal_memset(ptr, 0, total);
+    return ptr;
+  }
+  return REAL(calloc)(nmemb, size);
+}
+
+INTERCEPTOR(void *, realloc, void *ptr, uptr size) {
+  if (DlsymAlloc::Use() || DlsymAlloc::PointerIsMine(ptr))
+    return DlsymAlloc::Realloc(ptr, size);
+
+  // realloc(nullptr, size) == malloc(size)
+  if (!ptr) {
+    if (ShouldUseLowFat(size))
+      return __lowfat::Allocate(size);
+    return REAL(malloc)(size);
+  }
+
+  // realloc(ptr, 0) == free(ptr)
+  if (size == 0) {
+    if (__lowfat::IsLowFatPointer((uptr)ptr))
+      __lowfat::Deallocate(ptr);
+    else
+      REAL(free)(ptr);
+    return nullptr;
+  }
+
+  bool old_is_lowfat = __lowfat::IsLowFatPointer((uptr)ptr);
+
+  if (ShouldUseLowFat(size)) {
+    void *new_ptr = __lowfat::Allocate(size);
+    if (!new_ptr)
+      return nullptr;
+    // Copy old data. For LowFat pointers, old size = size class.
+    // For system pointers, we don't know exact old size, copy 'size' bytes.
+    uptr copy_size = size;
+    if (old_is_lowfat) {
+      uptr old_size = __lowfat::GetSize((uptr)ptr);
+      if (old_size < copy_size)
+        copy_size = old_size;
+    }
+    internal_memcpy(new_ptr, ptr, copy_size);
+    // Free old
+    if (old_is_lowfat)
+      __lowfat::Deallocate(ptr);
+    else
+      REAL(free)(ptr);
+    return new_ptr;
+  }
+
+  // New size exceeds LowFat max — use system realloc
+  if (old_is_lowfat) {
+    // Must migrate from LowFat to system
+    void *new_ptr = REAL(malloc)(size);
+    if (!new_ptr)
+      return nullptr;
+    uptr old_size = __lowfat::GetSize((uptr)ptr);
+    uptr copy_size = old_size < size ? old_size : size;
+    internal_memcpy(new_ptr, ptr, copy_size);
+    __lowfat::Deallocate(ptr);
+    return new_ptr;
+  }
+
+  return REAL(realloc)(ptr, size);
+}
+
+INTERCEPTOR(void *, valloc, uptr size) {
+  // valloc requires page-aligned allocation; fall back to system
+  return REAL(valloc)(size);
+}
+
+INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) {
+  // LowFat allocations are aligned to their size class, which may satisfy
+  // the alignment requirement. For now, fall back to system.
+  return REAL(posix_memalign)(memptr, alignment, size);
+}
+
+#if SANITIZER_APPLE
+INTERCEPTOR(uptr, malloc_size, void *ptr) {
+  if (DlsymAlloc::PointerIsMine(ptr))
+    return DlsymAlloc::GetSize(ptr);
+  if (__lowfat::IsLowFatPointer((uptr)ptr))
+    return __lowfat::GetSize((uptr)ptr);
+  return REAL(malloc_size)(ptr);
+}
+#endif
+
+//===----------------------------------------------------------------------===//
+// Initialization
+//===----------------------------------------------------------------------===//
+
+namespace __lowfat {
+void InitializeInterceptors() {
+  static int inited = 0;
+  CHECK_EQ(inited, 0);
+
+  INTERCEPT_FUNCTION(malloc);
+  INTERCEPT_FUNCTION(free);
+  INTERCEPT_FUNCTION(calloc);
+  INTERCEPT_FUNCTION(realloc);
+  INTERCEPT_FUNCTION(valloc);
+  INTERCEPT_FUNCTION(posix_memalign);
+#if SANITIZER_APPLE
+  INTERCEPT_FUNCTION(malloc_size);
+#endif
+
+  inited = 1;
+}
+} // namespace __lowfat
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 3451ad7599f52..06a5d1dab9b67 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -14,6 +14,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "lf_allocator.h"
 #include "lf_interface.h"
 #include "lf_config.h"
 #include "sanitizer_common/sanitizer_common.h"
@@ -24,8 +25,8 @@ using namespace __sanitizer;
 
 namespace __lowfat {
 
-// Flag to track initialization state
-static bool lowfat_inited = false;
+// Flag to track initialization state (not static — accessed by lf_interceptors.cpp)
+bool lowfat_inited = false;
 
 // Region table - initialized in __lf_init
 // TODO: not actually needed, to use for convenience
@@ -81,7 +82,7 @@ static bool InitMemoryRegions() {
 
 // Allocate from a LowFat region
 // First checks the free list, then falls back to bump allocation
-static void *Allocate(uptr size) {
+void *Allocate(uptr size) {
   // Printf("LowFat: allocating %zu bytes\n", size);
   if (size == 0)
     size = 1;
@@ -120,7 +121,7 @@ static void *Allocate(uptr size) {
 }
 
 // Free a LowFat allocation by adding it to the free list
-static void Deallocate(void *ptr) {
+void Deallocate(void *ptr) {
   if (!ptr)
     return;
   
@@ -194,6 +195,8 @@ void __lf_init() {
   // Printf("LowFat Sanitizer: initialized runtime\n");
 
   __lowfat::lowfat_inited = true;
+
+  __lowfat::InitializeInterceptors();
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE

>From b96c2469682692131fe99a2941df2bfe8979a2c4 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Mon, 16 Feb 2026 11:00:16 +0800
Subject: [PATCH 13/55] [LowFat] Implement inline bounds checking

Replace the __lf_check_bounds runtime call with inline LLVM IR to perform bounds checking directly
---
 .../Instrumentation/LowFatSanitizer.cpp       | 79 +++++++++++++++----
 1 file changed, 62 insertions(+), 17 deletions(-)

diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 0b57458fff0d2..b21f7005de618 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -24,6 +24,7 @@
 #include "llvm/IR/Module.h"
 #include "llvm/IR/Type.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Transforms/Utils/BasicBlockUtils.h"
 
 using namespace llvm;
 
@@ -45,8 +46,8 @@ class LowFatSanitizer {
   bool run();
 
 private:
-  /// Get or create the declaration for __lf_check_bounds.
-  FunctionCallee getCheckBoundsFn();
+  /// Get or create the declaration for __lf_report_oob.
+  FunctionCallee getReportOobFn();
 
   /// Instrument a single function.
   bool instrumentFunction(Function &F);
@@ -56,20 +57,27 @@ class LowFatSanitizer {
   bool instrumentMemoryAccess(Instruction *I, Value *Ptr, Type *AccessTy);
 
   Module &M;
-  const LowFatSanitizerOptions &Options;
+  const LowFatSanitizerOptions &Options; // TODO: impelement options
   const DataLayout &DL;
   Type *IntptrTy;
-  FunctionCallee CheckBoundsFn;
+  FunctionCallee ReportOobFn;
 };
 
-FunctionCallee LowFatSanitizer::getCheckBoundsFn() {
-  if (!CheckBoundsFn) {
-    // void __lf_check_bounds(uptr ptr, uptr size)
+// LowFat Constants (must match lf_config.h)
+static const uint64_t RegionBase = 0x100000000000ULL;
+static const uint64_t RegionSizeLog = 32;
+static const uint64_t MinSizeLog = 4;
+static const uint64_t NumSizeClasses = 27;
+
+FunctionCallee LowFatSanitizer::getReportOobFn() {
+  if (!ReportOobFn) {
+    // void __lf_report_oob(uptr ptr, uptr base, uptr size)
     Type *VoidTy = Type::getVoidTy(M.getContext());
-    CheckBoundsFn = M.getOrInsertFunction(
-        "__lf_check_bounds", FunctionType::get(VoidTy, {IntptrTy, IntptrTy}, false));
+    ReportOobFn = M.getOrInsertFunction(
+        "__lf_report_oob",
+        FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy}, false));
   }
-  return CheckBoundsFn;
+  return ReportOobFn;
 }
 
 bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
@@ -86,13 +94,50 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   // Convert pointer to integer
   Value *PtrInt = IRB.CreatePtrToInt(Ptr, IntptrTy);
 
-  // Create size constant
-  Value *SizeVal = ConstantInt::get(IntptrTy, AccessSize.getFixedValue());
-
-  // Insert call to __lf_check_bounds(ptr, size)
-  IRB.CreateCall(getCheckBoundsFn(), {PtrInt, SizeVal});
-
-  LLVM_DEBUG(dbgs() << "[LowFat] Instrumented: " << *I << "\n");
+  // 1. Get region index: (Ptr - RegionBase) >> RegionSizeLog
+  Value *RegionBaseVal = ConstantInt::get(IntptrTy, RegionBase);
+  Value *RegionOffset = IRB.CreateSub(PtrInt, RegionBaseVal);
+  Value *RegionIndex = IRB.CreateLShr(RegionOffset, RegionSizeLog);
+
+  // 2. Check if LowFat pointer: Region < NumSizeClasses
+  Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
+  Value *IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+
+  // Split block for the slow path (if LowFat)
+  Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
+  IRBuilder<> ThenIRB(ThenTerm);
+
+  // 3. Compute bounds inside the 'then' block
+  // Size = 1 << (Region + MinSizeLog)
+  Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
+  Value *ShiftAmount = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
+  Value *SizeOne = ConstantInt::get(IntptrTy, 1);
+  Value *AllocSize = ThenIRB.CreateShl(SizeOne, ShiftAmount);
+
+  // Mask = ~(AllocSize - 1)
+  Value *SizeMinusOne = ThenIRB.CreateSub(AllocSize, SizeOne);
+  Value *Mask = ThenIRB.CreateNot(SizeMinusOne);
+
+  // Base = Ptr & Mask
+  Value *Base = ThenIRB.CreateAnd(PtrInt, Mask);
+
+  // End = Base + AllocSize
+  Value *End = ThenIRB.CreateAdd(Base, AllocSize);
+
+  // 4. Check access: Ptr + AccessSize <= End
+  // Equivalent OOB check: (Ptr + AccessSize) > End
+  Value *AccessSizeVal = ConstantInt::get(IntptrTy, AccessSize.getFixedValue());
+  Value *AccessEnd = ThenIRB.CreateAdd(PtrInt, AccessSizeVal);
+  Value *IsOOB = ThenIRB.CreateICmpUGT(AccessEnd, End);
+
+  // Split again for OOB reporting
+  Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
+  IRBuilder<> OobIRB(OobTerm);
+  
+  // 5. Report error (slow path)
+  OobIRB.CreateCall(getReportOobFn(), {PtrInt, Base, AllocSize});
+
+  LLVM_DEBUG(dbgs() << "[LowFat] Instrumented (inline): " << *I << "\n");
   return true;
 }
 

>From 19efaf97a26a247cec23c415c39d48ecee691b61 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Mon, 16 Feb 2026 11:01:10 +0800
Subject: [PATCH 14/55] [LowFat] Update tests for inline bounds checking

---
 .../Instrumentation/LowFatSanitizer/basic.ll  | 46 +++++++++++--------
 1 file changed, 28 insertions(+), 18 deletions(-)

diff --git a/llvm/test/Instrumentation/LowFatSanitizer/basic.ll b/llvm/test/Instrumentation/LowFatSanitizer/basic.ll
index cbb4aa1046121..97588bb23bbc5 100644
--- a/llvm/test/Instrumentation/LowFatSanitizer/basic.ll
+++ b/llvm/test/Instrumentation/LowFatSanitizer/basic.ll
@@ -1,12 +1,15 @@
 ; RUN: opt < %s -passes=lowfat -S | FileCheck %s
 target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
 
-; Test 1: Load should be instrumented with __lf_check_bounds
+; Test 1: Load should be instrumented with inline checks
 define i32 @test_load(ptr %p) {
 ; CHECK-LABEL: @test_load
-; CHECK: %[[PTR:.*]] = ptrtoint ptr %p to i64
-; CHECK-NEXT: call void @__lf_check_bounds(i64 %[[PTR]], i64 4)
-; CHECK-NEXT: %val = load i32, ptr %p
+; CHECK: %[[PTR_INT:.*]] = ptrtoint ptr %p to i64
+; CHECK: sub i64 %[[PTR_INT]], 17592186044416
+; CHECK: lshr i64 {{.*}}, 32
+; CHECK: icmp ult i64 {{.*}}, 27
+; CHECK: call void @__lf_report_oob
+; CHECK: %val = load i32, ptr %p
   %val = load i32, ptr %p, align 4
   ret i32 %val
 }
@@ -14,9 +17,9 @@ define i32 @test_load(ptr %p) {
 ; Test 2: Store should be instrumented
 define void @test_store(ptr %p, i32 %v) {
 ; CHECK-LABEL: @test_store
-; CHECK: %[[PTR:.*]] = ptrtoint ptr %p to i64
-; CHECK-NEXT: call void @__lf_check_bounds(i64 %[[PTR]], i64 4)
-; CHECK-NEXT: store i32 %v, ptr %p
+; CHECK: ptrtoint ptr %p to i64
+; CHECK: call void @__lf_report_oob
+; CHECK: store i32 %v, ptr %p
   store i32 %v, ptr %p, align 4
   ret void
 }
@@ -24,7 +27,7 @@ define void @test_store(ptr %p, i32 %v) {
 ; Test 3: Volatile accesses should NOT be instrumented
 define i32 @test_volatile_load(ptr %p) {
 ; CHECK-LABEL: @test_volatile_load
-; CHECK-NOT: call void @__lf_check_bounds
+; CHECK-NOT: call void @__lf_report_oob
 ; CHECK: load volatile i32, ptr %p
   %val = load volatile i32, ptr %p, align 4
   ret i32 %val
@@ -32,23 +35,27 @@ define i32 @test_volatile_load(ptr %p) {
 
 define void @test_volatile_store(ptr %p, i32 %v) {
 ; CHECK-LABEL: @test_volatile_store
-; CHECK-NOT: call void @__lf_check_bounds
+; CHECK-NOT: call void @__lf_report_oob
 ; CHECK: store volatile i32 %v, ptr %p
   store volatile i32 %v, ptr %p, align 4
   ret void
 }
 
 ; Test 4: Runtime functions (__lf_*) should NOT be instrumented
-define void @__lf_check_bounds(i64 %ptr, i64 %size) {
-; CHECK-LABEL: @__lf_check_bounds
-; CHECK-NOT: call void @__lf_check_bounds
+define void @__lf_internal_test(ptr %p) {
+; CHECK-LABEL: @__lf_internal_test
+; CHECK-NOT: call void @__lf_report_oob
+  store i32 0, ptr %p
   ret void
 }
 
 ; Test 5: i8 load should use size 1
 define i8 @test_load_i8(ptr %p) {
 ; CHECK-LABEL: @test_load_i8
-; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 1)
+; CHECK: %[[PTR_INT:.*]] = ptrtoint ptr %p to i64
+; CHECK: add i64 %[[PTR_INT]], 1
+; CHECK-NEXT: icmp ugt
+; CHECK: call void @__lf_report_oob
   %val = load i8, ptr %p, align 1
   ret i8 %val
 }
@@ -56,7 +63,10 @@ define i8 @test_load_i8(ptr %p) {
 ; Test 6: i64 store should use size 8
 define void @test_store_i64(ptr %p, i64 %v) {
 ; CHECK-LABEL: @test_store_i64
-; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 8)
+; CHECK: %[[PTR_INT:.*]] = ptrtoint ptr %p to i64
+; CHECK: add i64 %[[PTR_INT]], 8
+; CHECK-NEXT: icmp ugt
+; CHECK: call void @__lf_report_oob
   store i64 %v, ptr %p, align 8
   ret void
 }
@@ -64,7 +74,7 @@ define void @test_store_i64(ptr %p, i64 %v) {
 ; Test 7: AtomicRMW should be instrumented
 define i32 @test_atomic_rmw(ptr %p) {
 ; CHECK-LABEL: @test_atomic_rmw
-; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 4)
+; CHECK: call void @__lf_report_oob
 ; CHECK: atomicrmw add ptr %p, i32 1
   %old = atomicrmw add ptr %p, i32 1 monotonic
   ret i32 %old
@@ -73,7 +83,7 @@ define i32 @test_atomic_rmw(ptr %p) {
 ; Test 8: AtomicCmpXchg should be instrumented
 define { i32, i1 } @test_atomic_cmpxchg(ptr %p) {
 ; CHECK-LABEL: @test_atomic_cmpxchg
-; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 4)
+; CHECK: call void @__lf_report_oob
 ; CHECK: cmpxchg ptr %p, i32 0, i32 1
   %val = cmpxchg ptr %p, i32 0, i32 1 monotonic monotonic
   ret { i32, i1 } %val
@@ -82,9 +92,9 @@ define { i32, i1 } @test_atomic_cmpxchg(ptr %p) {
 ; Test 9: Multiple accesses in one function
 define void @test_multiple(ptr %p, ptr %q) {
 ; CHECK-LABEL: @test_multiple
-; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 4)
+; CHECK: call void @__lf_report_oob
 ; CHECK: load i32
-; CHECK: call void @__lf_check_bounds(i64 %{{.*}}, i64 4)
+; CHECK: call void @__lf_report_oob
 ; CHECK: store i32
   %val = load i32, ptr %p, align 4
   store i32 %val, ptr %q, align 4

>From 749aa9e240872b1869f79d8c66dd5b677edaf9c2 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Mon, 16 Feb 2026 11:02:22 +0800
Subject: [PATCH 15/55] [LowFat] Remove __lf_check_bounds from runtime

---
 compiler-rt/lib/lowfat/lf_interface.h |  5 -----
 compiler-rt/lib/lowfat/lf_rtl.cpp     | 20 --------------------
 2 files changed, 25 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_interface.h b/compiler-rt/lib/lowfat/lf_interface.h
index 7e6e90c9cf0e0..3dd1e2028f42a 100644
--- a/compiler-rt/lib/lowfat/lf_interface.h
+++ b/compiler-rt/lib/lowfat/lf_interface.h
@@ -23,11 +23,6 @@ extern "C" {
 // Initialize the LowFat Sanitizer runtime. Called early during program startup.
 SANITIZER_INTERFACE_ATTRIBUTE void __lf_init();
 
-// Perform a bounds check on a pointer access.
-// ptr: The pointer being accessed
-// size: The size of the access in bytes
-SANITIZER_INTERFACE_ATTRIBUTE void __lf_check_bounds(uptr ptr, uptr size);
-
 // Report an out-of-bounds error.
 // ptr: The pointer that caused the violation
 // base: The base address of the allocation
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 06a5d1dab9b67..a53767b1f5e76 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -199,26 +199,6 @@ void __lf_init() {
   __lowfat::InitializeInterceptors();
 }
 
-SANITIZER_INTERFACE_ATTRIBUTE
-void __lf_check_bounds(uptr ptr, uptr size) {
-  // Printf("LowFat: check_bounds(ptr=0x%zx, size=%zu)", ptr, size);
-  if (!__lowfat::IsLowFatPointer(ptr)) {
-    // Printf(" → not LowFat, skipping\n");
-    return;
-  }
-  
-  uptr base = __lowfat::GetBase(ptr);
-  uptr alloc_size = __lowfat::GetSize(ptr);
-  bool in_bounds = __lowfat::CheckBounds(ptr, size);
-  // Printf(" → base=0x%zx, alloc=%zu, end=0x%zx, %s\n",
-  //        base, alloc_size, base + alloc_size,
-  //        in_bounds ? "OK" : "OOB!");
-
-  if (!in_bounds) {
-    __lowfat::PrintErrorAndDie(ptr, base, alloc_size);
-  }
-}
-
 SANITIZER_INTERFACE_ATTRIBUTE
 void __lf_report_oob(uptr ptr, uptr base, uptr bound) {
   __lowfat::PrintErrorAndDie(ptr, base, bound);

>From c9b7afe8c92e92f20d408ef55d9c3b618f4818fc Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sun, 1 Mar 2026 19:02:25 +0800
Subject: [PATCH 16/55] [LowFat] Add options parsing and warnings for recover

---
 clang/lib/CodeGen/BackendUtil.cpp             |  1 +
 .../cmake/Modules/AllSupportedArchDefs.cmake  |  7 +++-
 compiler-rt/lib/lowfat/lf_interface.h         |  7 ++++
 compiler-rt/lib/lowfat/lf_rtl.cpp             | 37 +++++++------------
 .../Instrumentation/LowFatSanitizer.h         |  2 +-
 llvm/lib/Passes/PassBuilder.cpp               | 11 ++++--
 .../Instrumentation/LowFatSanitizer.cpp       | 32 +++++++++++++---
 7 files changed, 64 insertions(+), 33 deletions(-)

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index a5a1da5334df1..ef576fb649e2d 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -771,6 +771,7 @@ static void addSanitizers(const Triple &TargetTriple,
 
     if (LangOpts.Sanitize.has(SanitizerKind::LowFat)) {
       LowFatSanitizerOptions Opts;
+      Opts.Recover = CodeGenOpts.SanitizeRecover.has(SanitizerKind::LowFat);
       MPM.addPass(LowFatSanitizerPass(Opts));
     }
   };
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 0015b0fffce81..5fbf41a86bb4b 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -122,7 +122,12 @@ set(ALL_XRAY_SUPPORTED_ARCH ${X86_64} ${ARM32} ${ARM64} ${MIPS32} ${MIPS64}
 endif()
 set(ALL_XRAY_DSO_SUPPORTED_ARCH ${X86_64} ${ARM64})
 set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64})
-set(ALL_LOWFAT_SUPPORTED_ARCH ${X86_64} ${ARM64})
+if(APPLE)
+  # Exclude x86_64h: lipo cannot combine x86_64 and x86_64h into one fat binary.
+  set(ALL_LOWFAT_SUPPORTED_ARCH x86_64 ${ARM64})
+else()
+  set(ALL_LOWFAT_SUPPORTED_ARCH ${X86_64} ${ARM64})
+endif()
 
 if (UNIX)
   if (OS_NAME MATCHES "Linux")
diff --git a/compiler-rt/lib/lowfat/lf_interface.h b/compiler-rt/lib/lowfat/lf_interface.h
index 3dd1e2028f42a..aba0b9b4047c9 100644
--- a/compiler-rt/lib/lowfat/lf_interface.h
+++ b/compiler-rt/lib/lowfat/lf_interface.h
@@ -30,6 +30,13 @@ SANITIZER_INTERFACE_ATTRIBUTE void __lf_init();
 SANITIZER_INTERFACE_ATTRIBUTE void __lf_report_oob(uptr ptr, uptr base,
                                                     uptr bound);
 
+// Warn about an out-of-bounds error without terminating.
+// ptr: The pointer that caused the violation
+// base: The base address of the allocation
+// bound: The size of the allocation
+SANITIZER_INTERFACE_ATTRIBUTE void __lf_warn_oob(uptr ptr, uptr base,
+                                                  uptr bound);
+
 // Get the base address of an allocation from a pointer.
 // Returns the base address, or 0 if the pointer is not within a LowFat region.
 SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_base(uptr ptr);
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index a53767b1f5e76..9a4f326c66f26 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -18,8 +18,6 @@
 #include "lf_interface.h"
 #include "lf_config.h"
 #include "sanitizer_common/sanitizer_common.h"
-#include "sanitizer_common/sanitizer_flags.h"
-#include "sanitizer_common/sanitizer_stacktrace.h"
 
 using namespace __sanitizer;
 
@@ -144,35 +142,23 @@ void Deallocate(void *ptr) {
   free_lists[region] = block;
 }
 
-static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound) {
-  Printf("=================================================================\n");
-  Printf("==ERROR: LowFat: out-of-bounds access detected\n");
+static void PrintOobHeader(const char *level, uptr ptr, uptr base, uptr bound) {
+  Printf("%s: LowFat: out-of-bounds access detected\n", level);
   Printf("  pointer: 0x%zx\n", ptr);
   Printf("  base:    0x%zx\n", base);
   Printf("  bound:   %zu bytes\n", bound);
-  Printf("=================================================================\n");
-
-  // Print stack trace
-  BufferedStackTrace stack;
-  stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr,
-               common_flags()->fast_unwind_on_fatal);
-  stack.Print();
+}
 
+static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound) {
+  PrintOobHeader("ERROR", ptr, base, bound);
   Die();
 }
 
-}  // namespace __lowfat
-
-namespace __sanitizer {
-void BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, void *context,
-                                    bool request_fast, u32 max_depth) {
-  uptr top = 0;
-  uptr bottom = 0;
-  GetThreadStackTopAndBottom(false, &top, &bottom);
-  bool fast = StackTrace::WillUseFastUnwind(request_fast);
-  Unwind(max_depth, pc, bp, context, top, bottom, fast);
+static void PrintWarning(uptr ptr, uptr base, uptr bound) {
+  PrintOobHeader("WARNING", ptr, base, bound);
 }
-}  // namespace __sanitizer
+
+}  // namespace __lowfat
 
 // ---------------------- Interface Functions ----------------------
 
@@ -204,6 +190,11 @@ void __lf_report_oob(uptr ptr, uptr base, uptr bound) {
   __lowfat::PrintErrorAndDie(ptr, base, bound);
 }
 
+SANITIZER_INTERFACE_ATTRIBUTE
+void __lf_warn_oob(uptr ptr, uptr base, uptr bound) {
+  __lowfat::PrintWarning(ptr, base, bound);
+}
+
 SANITIZER_INTERFACE_ATTRIBUTE
 uptr __lf_get_base(uptr ptr) {
   return __lowfat::GetBase(ptr);
diff --git a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
index 9a39b532a296d..3509937334423 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
@@ -14,7 +14,7 @@ namespace llvm {
 class Module;
 
 struct LowFatSanitizerOptions {
-  // LowFat currently does not support any options.
+  bool Recover = false;
 };
 
 class LowFatSanitizerPass : public PassInfoMixin<LowFatSanitizerPass> {
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index fa31ef272ef2b..ad5d78435617e 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -983,9 +983,14 @@ Expected<HWAddressSanitizerOptions> parseHWASanPassOptions(StringRef Params) {
 Expected<LowFatSanitizerOptions> parseLowFatPassOptions(StringRef Params) {
   LowFatSanitizerOptions Result;
   if (!Params.empty()) {
-    return make_error<StringError>(
-        formatv("invalid LowFatSanitizer pass parameter '{}'", Params).str(),
-        inconvertibleErrorCode());
+    for (StringRef Param : llvm::split(Params, ';')) {
+      if (Param == "recover")
+        Result.Recover = true;
+      else
+        return make_error<StringError>(
+            formatv("invalid LowFatSanitizer pass parameter '{}'", Param).str(),
+            inconvertibleErrorCode());
+    }
   }
   return Result;
 }
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index b21f7005de618..d99390503e9eb 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -46,9 +46,12 @@ class LowFatSanitizer {
   bool run();
 
 private:
-  /// Get or create the declaration for __lf_report_oob.
+  /// Get or create the declaration for __lf_report_oob (fatal).
   FunctionCallee getReportOobFn();
 
+  /// Get or create the declaration for __lf_warn_oob (non-fatal).
+  FunctionCallee getWarnOobFn();
+
   /// Instrument a single function.
   bool instrumentFunction(Function &F);
 
@@ -57,10 +60,11 @@ class LowFatSanitizer {
   bool instrumentMemoryAccess(Instruction *I, Value *Ptr, Type *AccessTy);
 
   Module &M;
-  const LowFatSanitizerOptions &Options; // TODO: impelement options
+  const LowFatSanitizerOptions &Options;
   const DataLayout &DL;
   Type *IntptrTy;
   FunctionCallee ReportOobFn;
+  FunctionCallee WarnOobFn;
 };
 
 // LowFat Constants (must match lf_config.h)
@@ -76,10 +80,25 @@ FunctionCallee LowFatSanitizer::getReportOobFn() {
     ReportOobFn = M.getOrInsertFunction(
         "__lf_report_oob",
         FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy}, false));
+    if (auto *F = dyn_cast<Function>(ReportOobFn.getCallee()))
+      F->addFnAttr(Attribute::NoReturn);
   }
   return ReportOobFn;
 }
 
+FunctionCallee LowFatSanitizer::getWarnOobFn() {
+  if (!WarnOobFn) {
+    // void __lf_warn_oob(uptr ptr, uptr base, uptr size)
+    Type *VoidTy = Type::getVoidTy(M.getContext());
+    WarnOobFn = M.getOrInsertFunction(
+        "__lf_warn_oob",
+        FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy}, false));
+    if (auto *F = dyn_cast<Function>(WarnOobFn.getCallee()))
+      F->addFnAttr(Attribute::NoUnwind);
+  }
+  return WarnOobFn;
+}
+
 bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
                                               Type *AccessTy) {
   // Skip if the access type size is not known at compile time
@@ -134,10 +153,13 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
   IRBuilder<> OobIRB(OobTerm);
   
-  // 5. Report error (slow path)
-  OobIRB.CreateCall(getReportOobFn(), {PtrInt, Base, AllocSize});
+  // 5. Report OOB (slow path)
+  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
+  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize});
 
-  LLVM_DEBUG(dbgs() << "[LowFat] Instrumented (inline): " << *I << "\n");
+  LLVM_DEBUG(dbgs() << "[LowFat] Instrumented (inline, "
+                    << (Options.Recover ? "recover" : "fatal")
+                    << "): " << *I << "\n");
   return true;
 }
 

>From 443b76974bf97244f08a75f781e114a5943daf88 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sun, 1 Mar 2026 19:50:06 +0800
Subject: [PATCH 17/55] [LowFat] Add spinlocks for thread safety on mem alloc
 and dealloc

---
 compiler-rt/lib/lowfat/lf_rtl.cpp | 56 ++++++++++++++++---------------
 1 file changed, 29 insertions(+), 27 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 9a4f326c66f26..e491d6818edb6 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -18,6 +18,7 @@
 #include "lf_interface.h"
 #include "lf_config.h"
 #include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_mutex.h"
 
 using namespace __sanitizer;
 
@@ -43,6 +44,11 @@ struct FreeBlock {
 };
 static FreeBlock *free_lists[kNumSizeClasses];
 
+// Per-size-class spin mutexes protecting region_next_alloc and free_lists.
+// Using one lock per size class allows concurrent allocation across different
+// size classes, which is the common case in multi-threaded programs.
+static StaticSpinMutex region_locks[kNumSizeClasses];
+
 static void InitRegionTable() {
   for (uptr i = 0; i < kNumSizeClasses; i++) {
     uptr size = SizeClassToSize(i);
@@ -79,20 +85,20 @@ static bool InitMemoryRegions() {
 }
 
 // Allocate from a LowFat region
-// First checks the free list, then falls back to bump allocation
+// First checks the free list, then falls back to bump allocation.
+// Thread-safe: protected by per-size-class spin mutex.
 void *Allocate(uptr size) {
-  // Printf("LowFat: allocating %zu bytes\n", size);
   if (size == 0)
     size = 1;
-  
+
   uptr class_index = SizeClassIndex(size);
-  if (class_index >= kNumSizeClasses) {
-    // Printf("LowFat: allocation size %zu exceeds max size class\n", size);
+  if (class_index >= kNumSizeClasses)
     return nullptr;
-  }
-  
+
   uptr alloc_size = SizeClassToSize(class_index);
-  
+
+  SpinMutexLock lock(&region_locks[class_index]);
+
   // 1. Try free list first
   FreeBlock *block = free_lists[class_index];
   if (block) {
@@ -101,42 +107,38 @@ void *Allocate(uptr size) {
     internal_memset(block, 0, alloc_size);
     return (void *)block;
   }
-  
+
   // 2. Fall back to bump allocation
   uptr region_end = GetRegionStart(class_index) + kRegionSize;
   uptr addr = region_next_alloc[class_index];
-  
+
   // Ensure alignment (should already be aligned)
   addr = (addr + alloc_size - 1) & ~(alloc_size - 1);
-  
-  if (addr + alloc_size > region_end) {
-    // Printf("LowFat: region %zu exhausted\n", class_index);
+
+  if (addr + alloc_size > region_end)
     return nullptr;
-  }
-  
+
   region_next_alloc[class_index] = addr + alloc_size;
   return (void *)addr;
 }
 
-// Free a LowFat allocation by adding it to the free list
+// Free a LowFat allocation by pushing it onto the free list.
+// Thread-safe: protected by per-size-class spin mutex.
 void Deallocate(void *ptr) {
   if (!ptr)
     return;
-  
+
   uptr addr = (uptr)ptr;
-  // Printf("LowFat: freeing ptr 0x%zx\n", addr);
-  
+
   // Validate this is a LowFat pointer
-  if (!IsLowFatPointer(addr)) {
-    // Printf("LowFat: __lf_free called on non-LowFat pointer 0x%zx\n", addr);
+  if (!IsLowFatPointer(addr))
     return;
-  }
-  
+
   uptr region = GetRegionIndex(addr);
-  // Printf("LowFat: freed region %zu (size class %zu bytes)\n", 
-  //        region, SizeClassToSize(region));
-  
-  // Add to the head of the free list for this size class
+
+  SpinMutexLock lock(&region_locks[region]);
+
+  // Push to the head of the free list for this size class
   FreeBlock *block = (FreeBlock *)ptr;
   block->next = free_lists[region];
   free_lists[region] = block;

>From 10fa2f419c5e042f4a930c24dbbcf9d772e9be14 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sun, 1 Mar 2026 20:25:39 +0800
Subject: [PATCH 18/55] [LowFat] Report write/read, overflow, and size on OOB

---
 compiler-rt/lib/lowfat/lf_interface.h         | 12 ++++---
 compiler-rt/lib/lowfat/lf_rtl.cpp             | 36 ++++++++++++-------
 .../Instrumentation/LowFatSanitizer.cpp       | 20 +++++++----
 3 files changed, 44 insertions(+), 24 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_interface.h b/compiler-rt/lib/lowfat/lf_interface.h
index aba0b9b4047c9..d21cf9062e3b3 100644
--- a/compiler-rt/lib/lowfat/lf_interface.h
+++ b/compiler-rt/lib/lowfat/lf_interface.h
@@ -27,15 +27,17 @@ SANITIZER_INTERFACE_ATTRIBUTE void __lf_init();
 // ptr: The pointer that caused the violation
 // base: The base address of the allocation
 // bound: The size of the allocation
+// Report a fatal out-of-bounds access and terminate.
+// ptr: the offending pointer, base: base of the allocation,
+// bound: size of the allocation, is_write: 1=write 0=read
 SANITIZER_INTERFACE_ATTRIBUTE void __lf_report_oob(uptr ptr, uptr base,
-                                                    uptr bound);
+                                                    uptr bound, int is_write);
 
 // Warn about an out-of-bounds error without terminating.
-// ptr: The pointer that caused the violation
-// base: The base address of the allocation
-// bound: The size of the allocation
+// ptr: the offending pointer, base: base of the allocation,
+// bound: size of the allocation, is_write: 1=write 0=read
 SANITIZER_INTERFACE_ATTRIBUTE void __lf_warn_oob(uptr ptr, uptr base,
-                                                  uptr bound);
+                                                  uptr bound, int is_write);
 
 // Get the base address of an allocation from a pointer.
 // Returns the base address, or 0 if the pointer is not within a LowFat region.
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index e491d6818edb6..59ee761f7bdd6 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -144,20 +144,30 @@ void Deallocate(void *ptr) {
   free_lists[region] = block;
 }
 
-static void PrintOobHeader(const char *level, uptr ptr, uptr base, uptr bound) {
-  Printf("%s: LowFat: out-of-bounds access detected\n", level);
-  Printf("  pointer: 0x%zx\n", ptr);
-  Printf("  base:    0x%zx\n", base);
-  Printf("  bound:   %zu bytes\n", bound);
+static void PrintOobHeader(const char *level, uptr ptr, uptr base, uptr bound,
+                           int is_write) {
+  // Compute the signed overflow: how many bytes past the end of the allocation
+  // the access reached. 0 means exactly at the boundary.
+  sptr overflow = (sptr)(ptr) - (sptr)(base + bound);
+  const char *op = is_write ? "write" : "read";
+
+  Printf("LOWFAT %s: out-of-bounds error detected!\n", level);
+  Printf("          operation = %s\n", op);
+  Printf("          pointer   = 0x%zx (heap)\n", ptr);
+  Printf("          base      = 0x%zx\n", base);
+  Printf("          size      = %zu\n", bound);
+  const char *sign = (overflow >= 0) ? "+" : "";
+  Printf("          overflow  = %s%zd\n", sign, (long)overflow);
+  Printf("\n");
 }
 
-static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound) {
-  PrintOobHeader("ERROR", ptr, base, bound);
+static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound, int is_write) {
+  PrintOobHeader("ERROR", ptr, base, bound, is_write);
   Die();
 }
 
-static void PrintWarning(uptr ptr, uptr base, uptr bound) {
-  PrintOobHeader("WARNING", ptr, base, bound);
+static void PrintWarning(uptr ptr, uptr base, uptr bound, int is_write) {
+  PrintOobHeader("WARNING", ptr, base, bound, is_write);
 }
 
 }  // namespace __lowfat
@@ -188,13 +198,13 @@ void __lf_init() {
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
-void __lf_report_oob(uptr ptr, uptr base, uptr bound) {
-  __lowfat::PrintErrorAndDie(ptr, base, bound);
+void __lf_report_oob(uptr ptr, uptr base, uptr bound, int is_write) {
+  __lowfat::PrintErrorAndDie(ptr, base, bound, is_write);
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
-void __lf_warn_oob(uptr ptr, uptr base, uptr bound) {
-  __lowfat::PrintWarning(ptr, base, bound);
+void __lf_warn_oob(uptr ptr, uptr base, uptr bound, int is_write) {
+  __lowfat::PrintWarning(ptr, base, bound, is_write);
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index d99390503e9eb..1e7ea2beea6c7 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -75,11 +75,12 @@ static const uint64_t NumSizeClasses = 27;
 
 FunctionCallee LowFatSanitizer::getReportOobFn() {
   if (!ReportOobFn) {
-    // void __lf_report_oob(uptr ptr, uptr base, uptr size)
+    // void __lf_report_oob(uptr ptr, uptr base, uptr size, i8 is_write)
     Type *VoidTy = Type::getVoidTy(M.getContext());
+    Type *I8Ty = Type::getInt8Ty(M.getContext());
     ReportOobFn = M.getOrInsertFunction(
         "__lf_report_oob",
-        FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy}, false));
+        FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy, I8Ty}, false));
     if (auto *F = dyn_cast<Function>(ReportOobFn.getCallee()))
       F->addFnAttr(Attribute::NoReturn);
   }
@@ -88,11 +89,12 @@ FunctionCallee LowFatSanitizer::getReportOobFn() {
 
 FunctionCallee LowFatSanitizer::getWarnOobFn() {
   if (!WarnOobFn) {
-    // void __lf_warn_oob(uptr ptr, uptr base, uptr size)
+    // void __lf_warn_oob(uptr ptr, uptr base, uptr size, i8 is_write)
     Type *VoidTy = Type::getVoidTy(M.getContext());
+    Type *I8Ty = Type::getInt8Ty(M.getContext());
     WarnOobFn = M.getOrInsertFunction(
         "__lf_warn_oob",
-        FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy}, false));
+        FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy, I8Ty}, false));
     if (auto *F = dyn_cast<Function>(WarnOobFn.getCallee()))
       F->addFnAttr(Attribute::NoUnwind);
   }
@@ -153,12 +155,18 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
   IRBuilder<> OobIRB(OobTerm);
   
-  // 5. Report OOB (slow path)
+  // 5. Report OOB (slow path) — pass is_write so the runtime can print
+  // "read" or "write" in the diagnostic.
   FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
-  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize});
+  Type *I8Ty = Type::getInt8Ty(M.getContext());
+  bool IsWrite = isa<StoreInst>(I) || isa<AtomicRMWInst>(I) ||
+                 isa<AtomicCmpXchgInst>(I);
+  Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
+  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
 
   LLVM_DEBUG(dbgs() << "[LowFat] Instrumented (inline, "
                     << (Options.Recover ? "recover" : "fatal")
+                    << ", " << (IsWrite ? "write" : "read")
                     << "): " << *I << "\n");
   return true;
 }

>From 2605f0cf90f304b7f52538d132a94cb17aacf80c Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Mon, 2 Mar 2026 10:54:33 +0800
Subject: [PATCH 19/55] [LowFat] Temporary fix for lipo arch tag for x86_64h

---
 compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake  | 7 +------
 compiler-rt/cmake/Modules/CompilerRTDarwinUtils.cmake | 7 +++++++
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 5fbf41a86bb4b..0015b0fffce81 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -122,12 +122,7 @@ set(ALL_XRAY_SUPPORTED_ARCH ${X86_64} ${ARM32} ${ARM64} ${MIPS32} ${MIPS64}
 endif()
 set(ALL_XRAY_DSO_SUPPORTED_ARCH ${X86_64} ${ARM64})
 set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64})
-if(APPLE)
-  # Exclude x86_64h: lipo cannot combine x86_64 and x86_64h into one fat binary.
-  set(ALL_LOWFAT_SUPPORTED_ARCH x86_64 ${ARM64})
-else()
-  set(ALL_LOWFAT_SUPPORTED_ARCH ${X86_64} ${ARM64})
-endif()
+set(ALL_LOWFAT_SUPPORTED_ARCH ${X86_64} ${ARM64})
 
 if (UNIX)
   if (OS_NAME MATCHES "Linux")
diff --git a/compiler-rt/cmake/Modules/CompilerRTDarwinUtils.cmake b/compiler-rt/cmake/Modules/CompilerRTDarwinUtils.cmake
index 069d76b78e10e..a387ef4a70ca2 100644
--- a/compiler-rt/cmake/Modules/CompilerRTDarwinUtils.cmake
+++ b/compiler-rt/cmake/Modules/CompilerRTDarwinUtils.cmake
@@ -144,6 +144,13 @@ function(darwin_test_archs os valid_archs)
     endif()
   endif()
 
+  # x86_64h maps to the same lipo architecture tag as x86_64, causing fat
+  # binary creation to fail when both are present. Sanitizer runtimes gain
+  # no benefit from Haswell-specific codegen, so drop it for macOS as well.
+  if(${os} STREQUAL "osx")
+    list(REMOVE_ITEM archs "x86_64h")
+  endif()
+
   if(${os} MATCHES "^ios$")
     message(STATUS "Disabling sanitizers armv7* slice for ios")
     list(FILTER archs EXCLUDE REGEX "armv7.*")

>From e8df0187338892ca5210a809d11dc8b788a2da17 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Mon, 2 Mar 2026 11:17:45 +0800
Subject: [PATCH 20/55] [LowFat] Detect OOB for memcpy and memset

---
 .../Instrumentation/LowFatSanitizer.cpp       | 99 ++++++++++++++++++-
 1 file changed, 94 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 1e7ea2beea6c7..d33a3ede4259d 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -21,6 +21,7 @@
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/InstIterator.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Module.h"
 #include "llvm/IR/Type.h"
 #include "llvm/Support/Debug.h"
@@ -33,6 +34,7 @@ using namespace llvm;
 STATISTIC(NumInstrumentedLoads, "Number of loads instrumented");
 STATISTIC(NumInstrumentedStores, "Number of stores instrumented");
 STATISTIC(NumInstrumentedAtomics, "Number of atomic operations instrumented");
+STATISTIC(NumInstrumentedMemIntrinsics, "Number of mem intrinsics instrumented");
 
 namespace {
 
@@ -55,10 +57,16 @@ class LowFatSanitizer {
   /// Instrument a single function.
   bool instrumentFunction(Function &F);
 
-  /// Instrument a memory access instruction.
+  /// Instrument a load/store/atomic with a compile-time-known access size.
   /// Returns true if instrumentation was inserted.
   bool instrumentMemoryAccess(Instruction *I, Value *Ptr, Type *AccessTy);
 
+  /// Instrument a mem intrinsic (memcpy/memset/memmove) with a runtime size.
+  /// Ptr is the pointer to check, SizeVal is the runtime length, IsWrite
+  /// indicates the direction. Returns true if instrumentation was inserted.
+  bool instrumentMemoryRange(Instruction *I, Value *Ptr, Value *SizeVal,
+                             bool IsWrite);
+
   Module &M;
   const LowFatSanitizerOptions &Options;
   const DataLayout &DL;
@@ -171,6 +179,64 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   return true;
 }
 
+bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
+                                            Value *SizeVal, bool IsWrite) {
+  // Same bounds-checking logic as instrumentMemoryAccess, but uses a runtime
+  // SizeVal instead of a compile-time constant AccessSize.
+  IRBuilder<> IRB(I);
+
+  // Cast pointer and size to intptr
+  Value *PtrInt = IRB.CreatePtrToInt(Ptr, IntptrTy);
+  // Zero-extend SizeVal to IntptrTy if needed (len may be i32 or i64)
+  Value *Size = IRB.CreateZExtOrTrunc(SizeVal, IntptrTy);
+
+  // 1. Get region index: (Ptr - RegionBase) >> RegionSizeLog
+  Value *RegionBaseVal = ConstantInt::get(IntptrTy, RegionBase);
+  Value *RegionOffset = IRB.CreateSub(PtrInt, RegionBaseVal);
+  Value *RegionIndex = IRB.CreateLShr(RegionOffset, RegionSizeLog);
+
+  // 2. Check if LowFat pointer: Region < NumSizeClasses
+  Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
+  Value *IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+
+  // Split block for the LowFat slow path
+  Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
+  IRBuilder<> ThenIRB(ThenTerm);
+
+  // 3. Compute allocation size from region index: 1 << (region + MinSizeLog)
+  Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
+  Value *ShiftAmount = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
+  Value *SizeOne = ConstantInt::get(IntptrTy, 1);
+  Value *AllocSize = ThenIRB.CreateShl(SizeOne, ShiftAmount);
+
+  // 4. Compute base: Ptr & ~(AllocSize - 1)
+  Value *SizeMinusOne = ThenIRB.CreateSub(AllocSize, SizeOne);
+  Value *Mask = ThenIRB.CreateNot(SizeMinusOne);
+  Value *Base = ThenIRB.CreateAnd(PtrInt, Mask);
+
+  // 5. Compute end of allocation and end of access range
+  Value *AllocEnd = ThenIRB.CreateAdd(Base, AllocSize);
+  Value *AccessEnd = ThenIRB.CreateAdd(PtrInt, Size);
+
+  // 6. OOB if access end exceeds allocation end
+  Value *IsOOB = ThenIRB.CreateICmpUGT(AccessEnd, AllocEnd);
+
+  // 7. Report OOB
+  Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
+  IRBuilder<> OobIRB(OobTerm);
+
+  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
+  Type *I8Ty = Type::getInt8Ty(M.getContext());
+  Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
+  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
+
+  LLVM_DEBUG(dbgs() << "[LowFat] Instrumented mem intrinsic ("
+                    << (Options.Recover ? "recover" : "fatal")
+                    << ", " << (IsWrite ? "write" : "read")
+                    << "): " << *I << "\n");
+  return true;
+}
+
 bool LowFatSanitizer::instrumentFunction(Function &F) {
   // Skip functions that shouldn't be instrumented
   if (F.isDeclaration())
@@ -188,9 +254,15 @@ bool LowFatSanitizer::instrumentFunction(Function &F) {
 
   bool Modified = false;
 
-  // Collect instructions to instrument first to avoid iterator invalidation
+  // Track two kinds of instrumentation targets:
+  //   1. Load/store/atomic: compile-time access size, from instruction type
+  //   2. Mem intrinsics: runtime access size (the 'len' argument)
   SmallVector<std::pair<Instruction *, std::pair<Value *, Type *>>, 16> ToInstrument;
 
+  // Each MemRange entry is {I, Ptr, SizeVal, IsWrite}
+  struct MemRange { Instruction *I; Value *Ptr; Value *Size; bool IsWrite; };
+  SmallVector<MemRange, 8> MemRanges;
+
   for (Instruction &I : instructions(F)) {
     Value *Ptr = nullptr;
     Type *AccessTy = nullptr;
@@ -215,14 +287,23 @@ bool LowFatSanitizer::instrumentFunction(Function &F) {
         Ptr = AI->getPointerOperand();
         AccessTy = AI->getCompareOperand()->getType();
       }
+    } else if (auto *MI = dyn_cast<MemSetInst>(&I)) {
+      // memset(dst, val, len) — write-range check on dst
+      if (!MI->isVolatile())
+        MemRanges.push_back({&I, MI->getDest(), MI->getLength(), /*IsWrite=*/true});
+    } else if (auto *MI = dyn_cast<MemTransferInst>(&I)) {
+      // memcpy/memmove(dst, src, len) — write dst, read src
+      if (!MI->isVolatile()) {
+        MemRanges.push_back({&I, MI->getDest(), MI->getLength(), /*IsWrite=*/true});
+        MemRanges.push_back({&I, MI->getSource(), MI->getLength(), /*IsWrite=*/false});
+      }
     }
 
-    if (Ptr && AccessTy) {
+    if (Ptr && AccessTy)
       ToInstrument.push_back({&I, {Ptr, AccessTy}});
-    }
   }
 
-  // Now instrument collected instructions
+  // Instrument load/store/atomics
   for (auto &Entry : ToInstrument) {
     Instruction *I = Entry.first;
     Value *Ptr = Entry.second.first;
@@ -239,6 +320,14 @@ bool LowFatSanitizer::instrumentFunction(Function &F) {
     }
   }
 
+  // Instrument mem intrinsics
+  for (auto &MR : MemRanges) {
+    if (instrumentMemoryRange(MR.I, MR.Ptr, MR.Size, MR.IsWrite)) {
+      Modified = true;
+      ++NumInstrumentedMemIntrinsics;
+    }
+  }
+
   return Modified;
 }
 

>From 6b381e613e54eef617c483dc02d1da65fbd5daff Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Mon, 2 Mar 2026 11:20:23 +0800
Subject: [PATCH 21/55] [LowFat] Fix OOB overflow sign by passing AccessEnd

---
 llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index d33a3ede4259d..0f6d5d300e13e 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -228,7 +228,7 @@ bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
   FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
   Type *I8Ty = Type::getInt8Ty(M.getContext());
   Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
-  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
+  OobIRB.CreateCall(OobFn, {AccessEnd, Base, AllocSize, IsWriteVal});
 
   LLVM_DEBUG(dbgs() << "[LowFat] Instrumented mem intrinsic ("
                     << (Options.Recover ? "recover" : "fatal")

>From 0528297bace13a1cbfc2b120874a092982a3449c Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 12:38:51 +0800
Subject: [PATCH 22/55] [LowFat] Remove commented prints

---
 compiler-rt/lib/lowfat/lf_rtl.cpp | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 59ee761f7bdd6..a6593fba796f5 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -181,16 +181,10 @@ void __lf_init() {
   if (__lowfat::lowfat_inited)
     return;
 
-  // Printf("LowFat Sanitizer: initializing runtime\n");
-
   __lowfat::InitRegionTable();
   
-  if (!__lowfat::InitMemoryRegions()) {
-    // Printf("LowFat Sanitizer: failed to initialize memory regions\n");
+  if (!__lowfat::InitMemoryRegions())
     Die();
-  }
-
-  // Printf("LowFat Sanitizer: initialized runtime\n");
 
   __lowfat::lowfat_inited = true;
 

>From dd45c7a8e9d18cd4f23bfa43b1b24a7ea2f6f243 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 13:20:29 +0800
Subject: [PATCH 23/55] [LowFat] Kill process with internal__exit instead of
 Die()

---
 compiler-rt/lib/lowfat/lf_rtl.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index a6593fba796f5..466bf0bdc4841 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -163,7 +163,7 @@ static void PrintOobHeader(const char *level, uptr ptr, uptr base, uptr bound,
 
 static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound, int is_write) {
   PrintOobHeader("ERROR", ptr, base, bound, is_write);
-  Die();
+  internal__exit(1);
 }
 
 static void PrintWarning(uptr ptr, uptr base, uptr bound, int is_write) {

>From 6793bde50fe6d103bd8f7bafb6c4964f388491b4 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 13:22:07 +0800
Subject: [PATCH 24/55] [LowFat] Move LowFat to run before optimization

---
 clang/lib/CodeGen/BackendUtil.cpp | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index ef576fb649e2d..2dad552af2928 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -768,12 +768,7 @@ static void addSanitizers(const Triple &TargetTriple,
       MPM.addPass(DataFlowSanitizerPass(LangOpts.NoSanitizeFiles,
                                         PB.getVirtualFileSystemPtr()));
     }
-
-    if (LangOpts.Sanitize.has(SanitizerKind::LowFat)) {
-      LowFatSanitizerOptions Opts;
-      Opts.Recover = CodeGenOpts.SanitizeRecover.has(SanitizerKind::LowFat);
-      MPM.addPass(LowFatSanitizerPass(Opts));
-    }
+  // TODO: move LowFat sanitizer back here.
   };
   if (ClSanitizeOnOptimizerEarlyEP) {
     PB.registerOptimizerEarlyEPCallback(
@@ -791,6 +786,19 @@ static void addSanitizers(const Triple &TargetTriple,
     // LastEP does not need GlobalsAA.
     PB.registerOptimizerLastEPCallback(SanitizersCallback);
   }
+
+  // LowFat must run BEFORE any optimization or attribute inference so that the
+  // memory accesses it needs to instrument are not eliminated by DCE/DSE first.
+  // OptimizerEarlyEP is too late (InferFunctionAttrs already ran); use
+  // PipelineStart which fires before any analysis or optimization pass.
+  if (LangOpts.Sanitize.has(SanitizerKind::LowFat)) {
+    LowFatSanitizerOptions LFOpts;
+    LFOpts.Recover = CodeGenOpts.SanitizeRecover.has(SanitizerKind::LowFat);
+    PB.registerPipelineStartEPCallback(
+        [LFOpts](ModulePassManager &MPM, OptimizationLevel) {
+          MPM.addPass(LowFatSanitizerPass(LFOpts));
+        });
+  }
 }
 
 void addLowerAllowCheckPass(const CodeGenOptions &CodeGenOpts,

>From 73a11fc6284409b29e7aa249e4b2ccfb6ba51b2c Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 13:27:32 +0800
Subject: [PATCH 25/55] [LowFat] Fix test with new error text

---
 compiler-rt/test/lowfat/basic_inbounds.cpp     |  7 +++++--
 compiler-rt/test/lowfat/cross_boundary_oob.cpp |  2 +-
 compiler-rt/test/lowfat/free_list_reuse.cpp    | 11 ++++++-----
 3 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/compiler-rt/test/lowfat/basic_inbounds.cpp b/compiler-rt/test/lowfat/basic_inbounds.cpp
index 05e6290614e12..9184c025878f6 100644
--- a/compiler-rt/test/lowfat/basic_inbounds.cpp
+++ b/compiler-rt/test/lowfat/basic_inbounds.cpp
@@ -4,11 +4,12 @@
 // Verify that the LowFat runtime initializes and basic in-bounds
 // allocations work without errors.
 
+#include <cstdio>
+
 extern "C" void *__lf_malloc(unsigned long size);
 extern "C" void __lf_free(void *ptr);
 
 int main() {
-  // CHECK: LowFat Sanitizer: initialized runtime
   int *arr = (int *)__lf_malloc(10 * sizeof(int));
   if (!arr)
     return 1;
@@ -18,6 +19,8 @@ int main() {
   arr[9] = 99;
 
   __lf_free(arr);
-  // CHECK-NOT: ERROR: LowFat
+  // CHECK: basic_inbounds: ok
+  // CHECK-NOT: LOWFAT ERROR
+  printf("basic_inbounds: ok\n");
   return 0;
 }
diff --git a/compiler-rt/test/lowfat/cross_boundary_oob.cpp b/compiler-rt/test/lowfat/cross_boundary_oob.cpp
index 57dfa1434a032..3f1ec753b7efd 100644
--- a/compiler-rt/test/lowfat/cross_boundary_oob.cpp
+++ b/compiler-rt/test/lowfat/cross_boundary_oob.cpp
@@ -19,7 +19,7 @@ int main() {
 
   // Cross-boundary OOB: write 8 bytes at offset 12
   // ptr + 8 = buf+20 > buf+16 → out of bounds
-  // CHECK: ERROR: LowFat: out-of-bounds access detected
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   double *cross = (double *)(buf + 12);
   *cross = 3.14;
 
diff --git a/compiler-rt/test/lowfat/free_list_reuse.cpp b/compiler-rt/test/lowfat/free_list_reuse.cpp
index 6460277ed5f81..27ab57e000eb1 100644
--- a/compiler-rt/test/lowfat/free_list_reuse.cpp
+++ b/compiler-rt/test/lowfat/free_list_reuse.cpp
@@ -1,25 +1,26 @@
 // RUN: %clangxx_lowfat %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
-// Verify that allocation and free list reuse works correctly.
+// Verify that allocation and free list reuse works correctly, with no OOB errors.
+
+#include <cstdio>
 
 extern "C" void *__lf_malloc(unsigned long size);
 extern "C" void __lf_free(void *ptr);
 
 int main() {
-  // CHECK: LowFat Sanitizer: initialized runtime
-
   // Allocate and free, then allocate again — should reuse from free list
   int *a = (int *)__lf_malloc(10 * sizeof(int));
   a[0] = 1;
   __lf_free(a);
 
   int *b = (int *)__lf_malloc(10 * sizeof(int));
-  // After free list reuse, b should equal a (same address reused)
   b[0] = 2;
   b[9] = 3;
   __lf_free(b);
 
-  // CHECK-NOT: ERROR: LowFat
+  // CHECK: free_list_reuse: ok
+  // CHECK-NOT: LOWFAT ERROR
+  printf("free_list_reuse: ok\n");
   return 0;
 }

>From cb646b5000a917163107e6c7279fe63160e10408 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 13:30:26 +0800
Subject: [PATCH 26/55] [LowFat] Scaffold testing infrastructure

---
 compiler-rt/test/lowfat/CMakeLists.txt        | 31 ++++++++--
 .../test/lowfat/inbounds_no_report.cpp        | 33 +++++++++++
 compiler-rt/test/lowfat/lit.cfg.py            | 57 +++++++++++++++++++
 compiler-rt/test/lowfat/lit.site.cfg.py.in    | 13 +++++
 compiler-rt/test/lowfat/memcpy_oob_fatal.cpp  | 25 ++++++++
 .../test/lowfat/memcpy_oob_recover.cpp        | 30 ++++++++++
 compiler-rt/test/lowfat/memset_oob_fatal.cpp  | 22 +++++++
 .../test/lowfat/memset_oob_recover.cpp        | 27 +++++++++
 compiler-rt/test/lowfat/oob_read_fatal.cpp    | 26 +++++++++
 9 files changed, 260 insertions(+), 4 deletions(-)
 create mode 100644 compiler-rt/test/lowfat/inbounds_no_report.cpp
 create mode 100644 compiler-rt/test/lowfat/lit.cfg.py
 create mode 100644 compiler-rt/test/lowfat/lit.site.cfg.py.in
 create mode 100644 compiler-rt/test/lowfat/memcpy_oob_fatal.cpp
 create mode 100644 compiler-rt/test/lowfat/memcpy_oob_recover.cpp
 create mode 100644 compiler-rt/test/lowfat/memset_oob_fatal.cpp
 create mode 100644 compiler-rt/test/lowfat/memset_oob_recover.cpp
 create mode 100644 compiler-rt/test/lowfat/oob_read_fatal.cpp

diff --git a/compiler-rt/test/lowfat/CMakeLists.txt b/compiler-rt/test/lowfat/CMakeLists.txt
index 651b543e51a02..089d35f63d163 100644
--- a/compiler-rt/test/lowfat/CMakeLists.txt
+++ b/compiler-rt/test/lowfat/CMakeLists.txt
@@ -1,11 +1,34 @@
 set(LOWFAT_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
-set(LOWFAT_LIT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
 
 set(LOWFAT_TESTSUITES)
 set(LOWFAT_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
+list(APPEND LOWFAT_TEST_DEPS lowfat)
 
-if(COMPILER_RT_HAS_LOWFAT)
-  list(APPEND LOWFAT_TEST_DEPS lowfat)
+set(LOWFAT_TEST_ARCH ${LOWFAT_SUPPORTED_ARCH})
+if(APPLE)
+  darwin_filter_host_archs(LOWFAT_SUPPORTED_ARCH LOWFAT_TEST_ARCH)
 endif()
 
-# TODO: add tests for lowfat
+foreach(arch ${LOWFAT_TEST_ARCH})
+  set(LOWFAT_TEST_TARGET_ARCH ${arch})
+  # Use COMPILER_RT_TEST_COMPILER directly — get_test_cc_for_arch only populates
+  # the CC output when cross-compiling; on Darwin native builds it returns empty.
+  set(LOWFAT_TEST_TARGET_CC ${COMPILER_RT_TEST_COMPILER})
+  get_test_cc_for_arch(${arch} _unused LOWFAT_TEST_TARGET_CFLAGS)
+  string(TOLOWER "-${arch}-${OS_NAME}" LOWFAT_TEST_CONFIG_SUFFIX)
+  string(TOUPPER ${arch} ARCH_UPPER_CASE)
+  set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}Config)
+
+  configure_lit_site_cfg(
+    ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
+    ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py
+    MAIN_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py"
+  )
+  list(APPEND LOWFAT_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+endforeach()
+
+add_lit_testsuite(check-lowfat "Running LowFat Sanitizer tests"
+  ${LOWFAT_TESTSUITES}
+  DEPENDS ${LOWFAT_TEST_DEPS}
+)
+set_target_properties(check-lowfat PROPERTIES FOLDER "Compiler-RT/Tests")
diff --git a/compiler-rt/test/lowfat/inbounds_no_report.cpp b/compiler-rt/test/lowfat/inbounds_no_report.cpp
new file mode 100644
index 0000000000000..0c4863e26a87d
--- /dev/null
+++ b/compiler-rt/test/lowfat/inbounds_no_report.cpp
@@ -0,0 +1,33 @@
+// RUN: %clangxx_lowfat %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s
+
+// Verify that purely in-bounds accesses — including memcpy and memset within
+// the allocation size — do not trigger any OOB report.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+int main() {
+  char *buf = (char *)malloc(32);
+  if (!buf) return 1;
+
+  // Scalar: first and last byte.
+  buf[0]  = 'A';
+  buf[31] = 'Z';
+
+  // memcpy exactly fits.
+  const char src[32] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+  memcpy(buf, src, 32);
+
+  // memset exactly fits.
+  memset(buf, 0, 32);
+
+  // CHECK: OK
+  // CHECK-NOT: ERROR: LowFat
+  // CHECK-NOT: WARNING: LowFat
+  printf("OK\n");
+
+  free(buf);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/lit.cfg.py b/compiler-rt/test/lowfat/lit.cfg.py
new file mode 100644
index 0000000000000..993b5ba162dc1
--- /dev/null
+++ b/compiler-rt/test/lowfat/lit.cfg.py
@@ -0,0 +1,57 @@
+import lit.formats
+import os
+
+# Setup config name.
+config.name = "LowFatSanitizer" + getattr(config, "name_suffix", "")
+
+# Setup source root.
+config.test_source_root = os.path.dirname(__file__)
+config.suffixes = [".c", ".cpp"]
+
+# Teach lit that these are shell tests (// RUN: ... lines).
+# When loaded via the build-dir site config, lit.common.configured sets this;
+# when invoked directly against the source dir, we must set it ourselves.
+if not hasattr(config, "test_format"):
+    config.test_format = lit.formats.ShTest(execute_external=True)
+
+
+# Find clang++:
+#   1. config.clang is set by lit.common.configured (when run via build-dir site config)
+#   2. config.llvm_tools_dir is set by lit.common.configured
+#   3. Fall back to bare "clang++" on PATH for direct source-dir invocations
+def _find_clang():
+    clang = getattr(config, "clang", None)
+    if clang:
+        return clang
+    tools_dir = getattr(config, "llvm_tools_dir", None)
+    if tools_dir:
+        candidate = os.path.join(tools_dir, "clang++")
+        if os.path.isfile(candidate):
+            return candidate
+    return "clang++"
+
+
+clang = _find_clang()
+
+
+def build_invocation(flags):
+    return " " + " ".join([clang] + flags) + " "
+
+
+# Fatal mode  : terminate on first OOB
+lowfat_flags = ["-fsanitize=lowfat", "-O1"]
+# Recover mode: warn + continue
+lowfat_recover_flags = lowfat_flags + ["-fsanitize-recover=lowfat"]
+
+config.substitutions.append(("%clangxx_lowfat ", build_invocation(lowfat_flags)))
+config.substitutions.append(
+    ("%clangxx_lowfat_recover ", build_invocation(lowfat_recover_flags))
+)
+
+# Only Darwin and Linux are supported.
+if getattr(config, "target_os", "Unknown") not in ["Darwin", "Linux"]:
+    # When running directly (no site config), target_os may be unset; don't
+    # mark unsupported in that case — let the tests fail naturally if the
+    # platform truly isn't supported.
+    if hasattr(config, "target_os"):
+        config.unsupported = True
diff --git a/compiler-rt/test/lowfat/lit.site.cfg.py.in b/compiler-rt/test/lowfat/lit.site.cfg.py.in
new file mode 100644
index 0000000000000..1dbb2cb1367b6
--- /dev/null
+++ b/compiler-rt/test/lowfat/lit.site.cfg.py.in
@@ -0,0 +1,13 @@
+ at LIT_SITE_CFG_IN_HEADER@
+
+# Tool-specific config options.
+config.name_suffix = "@LOWFAT_TEST_CONFIG_SUFFIX@"
+config.target_cflags = "@LOWFAT_TEST_TARGET_CFLAGS@"
+config.clang = "@LOWFAT_TEST_TARGET_CC@"
+config.target_arch = "@LOWFAT_TEST_TARGET_ARCH@"
+
+# Load common config for all compiler-rt lit tests.
+lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")
+
+# Load tool-specific config that would do the real work.
+lit_config.load_config(config, "@LOWFAT_LIT_SOURCE_DIR@/lit.cfg.py")
diff --git a/compiler-rt/test/lowfat/memcpy_oob_fatal.cpp b/compiler-rt/test/lowfat/memcpy_oob_fatal.cpp
new file mode 100644
index 0000000000000..23f29460211fb
--- /dev/null
+++ b/compiler-rt/test/lowfat/memcpy_oob_fatal.cpp
@@ -0,0 +1,25 @@
+// RUN: %clangxx_lowfat %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// Verify that memcpy writing past the end of a LowFat allocation is detected
+// in fatal mode.  The process must exit with a non-zero code (checked by
+// "not %run") and print the expected diagnostic.
+
+#include <cstdlib>
+#include <cstring>
+
+int main() {
+  char *dst   = (char *)malloc(16);
+  char *guard = (char *)malloc(16); // keep adjacent memory mapped
+  if (!dst || !guard) return 1;
+
+  const char payload[32] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+  // memcpy of 32 bytes into a 16-byte allocation — overflows by 16 bytes.
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
+  memcpy(dst, payload, 32);
+
+  free(guard);
+  free(dst);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/memcpy_oob_recover.cpp b/compiler-rt/test/lowfat/memcpy_oob_recover.cpp
new file mode 100644
index 0000000000000..8257a6b1db46a
--- /dev/null
+++ b/compiler-rt/test/lowfat/memcpy_oob_recover.cpp
@@ -0,0 +1,30 @@
+// RUN: %clangxx_lowfat_recover %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s
+
+// Verify that memcpy OOB in recover mode:
+//   1. Prints a WARNING (not ERROR).
+//   2. Execution continues past the call (process exits 0).
+//   3. The reported overflow is positive (access end past allocation end).
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+int main() {
+  char *dst   = (char *)malloc(16);
+  char *guard = (char *)malloc(16);
+  if (!dst || !guard) return 1;
+
+  const char payload[32] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+  // CHECK: LOWFAT WARNING: out-of-bounds error detected!
+  memcpy(dst, payload, 32);
+
+  // Execution must reach here in recover mode.
+  // CHECK: after memcpy
+  printf("after memcpy\n");
+
+  free(guard);
+  free(dst);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/memset_oob_fatal.cpp b/compiler-rt/test/lowfat/memset_oob_fatal.cpp
new file mode 100644
index 0000000000000..aaa511dbfcb5d
--- /dev/null
+++ b/compiler-rt/test/lowfat/memset_oob_fatal.cpp
@@ -0,0 +1,22 @@
+// RUN: %clangxx_lowfat %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// Verify that memset writing past the end of a LowFat allocation is detected
+// in fatal mode.
+
+#include <cstdlib>
+#include <cstring>
+
+int main() {
+  char *dst   = (char *)malloc(16);
+  char *guard = (char *)malloc(16); // keep adjacent memory mapped
+  if (!dst || !guard) return 1;
+
+  // memset of 32 bytes into a 16-byte allocation — overflows by 16 bytes.
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
+  memset(dst, 0, 32);
+
+  free(guard);
+  free(dst);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/memset_oob_recover.cpp b/compiler-rt/test/lowfat/memset_oob_recover.cpp
new file mode 100644
index 0000000000000..36d9d921fbe2e
--- /dev/null
+++ b/compiler-rt/test/lowfat/memset_oob_recover.cpp
@@ -0,0 +1,27 @@
+// RUN: %clangxx_lowfat_recover %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s
+
+// Verify that memset OOB in recover mode warns and continues.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+int main() {
+  char *dst   = (char *)malloc(16);
+  char *guard = (char *)malloc(16);
+  if (!dst || !guard) return 1;
+
+  // FIXME: memset with a compile-time constant ensures clang emits the llvm.memset
+  // intrinsic, which the LowFat pass instruments. A runtime size would become
+  // a plain libc call that the pass cannot see.
+  // CHECK: LOWFAT WARNING: out-of-bounds error detected!
+  memset(dst, 0, 32); // 32 bytes into a 16-byte allocation → OOB
+
+  // CHECK: after memset
+  printf("after memset\n");
+
+  free(guard);
+  free(dst);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/oob_read_fatal.cpp b/compiler-rt/test/lowfat/oob_read_fatal.cpp
new file mode 100644
index 0000000000000..6b8c33e4a0ab1
--- /dev/null
+++ b/compiler-rt/test/lowfat/oob_read_fatal.cpp
@@ -0,0 +1,26 @@
+// RUN: %clangxx_lowfat %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// Verify that an OOB load (scalar read across allocation boundary) is detected
+// in fatal mode.
+
+#include <cstdlib>
+
+int main() {
+  char *buf = (char *)malloc(32);
+  if (!buf) return 1;
+
+  buf[0] = 'H';
+  buf[31] = 'i';
+
+  // Read 8 bytes at offset 28 of a 32-byte allocation:
+  //   bytes 28–35 exceed the 32-byte boundary → OOB.
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
+  // FIXME: must NOT use volatile here — the LowFat pass skips volatile accesses.
+  double *p = (double *)(buf + 28);
+  double val = *p; // 8-byte read at offset 28 of 32-byte alloc → OOB (bytes 28–35)
+  (void)val;       // keep live to prevent DSE; crash fires on the load above
+
+  free(buf);
+  return 0;
+}

>From 5ccd44990782940ec8b6b319b075cef6cad3a5b8 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 13:42:44 +0800
Subject: [PATCH 27/55] [LowFat] Initialize flags to Die() on error and resolve
 workaround

---
 compiler-rt/lib/lowfat/lf_rtl.cpp | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 466bf0bdc4841..3977445520e28 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -15,9 +15,11 @@
 //===----------------------------------------------------------------------===//
 
 #include "lf_allocator.h"
-#include "lf_interface.h"
 #include "lf_config.h"
+#include "lf_interface.h"
 #include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_flag_parser.h"
+#include "sanitizer_common/sanitizer_flags.h"
 #include "sanitizer_common/sanitizer_mutex.h"
 
 using namespace __sanitizer;
@@ -49,6 +51,26 @@ static FreeBlock *free_lists[kNumSizeClasses];
 // size classes, which is the common case in multi-threaded programs.
 static StaticSpinMutex region_locks[kNumSizeClasses];
 
+static void InitializeFlags() {
+  SetCommonFlagsDefaults();
+
+  {
+    CommonFlags cf;
+    cf.CopyFrom(*common_flags());
+    cf.exitcode = 1; // Fatal OOB exits with code 1 by default
+    cf.abort_on_error = false; // Use the exitcode path, not SIGABRT, so output is flushed before the process exits
+    OverrideCommonFlags(cf);
+  }
+
+  // Register all common flags with a parser and read LOWFAT_OPTIONS.
+  //    Allow overriding flags at runtime, e.g.: LOWFAT_OPTIONS=exitcode=42:verbosity=1 ./my_program
+  FlagParser parser;
+  RegisterCommonFlags(&parser);
+  parser.ParseStringFromEnv("LOWFAT_OPTIONS");
+
+  InitializeCommonFlags();
+}
+
 static void InitRegionTable() {
   for (uptr i = 0; i < kNumSizeClasses; i++) {
     uptr size = SizeClassToSize(i);
@@ -163,7 +185,7 @@ static void PrintOobHeader(const char *level, uptr ptr, uptr base, uptr bound,
 
 static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound, int is_write) {
   PrintOobHeader("ERROR", ptr, base, bound, is_write);
-  internal__exit(1);
+  Die();
 }
 
 static void PrintWarning(uptr ptr, uptr base, uptr bound, int is_write) {
@@ -181,8 +203,10 @@ void __lf_init() {
   if (__lowfat::lowfat_inited)
     return;
 
+  __lowfat::InitializeFlags();
+
   __lowfat::InitRegionTable();
-  
+
   if (!__lowfat::InitMemoryRegions())
     Die();
 

>From 068ff0d5e8d279e98b8c7cb7ebe0d91cfac4bb99 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 14:02:45 +0800
Subject: [PATCH 28/55] [LowFat] Add tests for libc memcpy, memsetand memmove

---
 .../test/lowfat/memcpy_oob_dynamic_fatal.cpp  | 28 +++++++++++++++++++
 ..._fatal.cpp => memcpy_oob_static_fatal.cpp} |  0
 ...over.cpp => memcpy_oob_static_recover.cpp} |  0
 .../test/lowfat/memmove_oob_dynamic_fatal.cpp | 25 +++++++++++++++++
 .../test/lowfat/memset_oob_dynamic_fatal.cpp  | 27 ++++++++++++++++++
 ..._fatal.cpp => memset_oob_static_fatal.cpp} |  0
 ...over.cpp => memset_oob_static_recover.cpp} |  6 ++--
 7 files changed, 82 insertions(+), 4 deletions(-)
 create mode 100644 compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
 rename compiler-rt/test/lowfat/{memcpy_oob_fatal.cpp => memcpy_oob_static_fatal.cpp} (100%)
 rename compiler-rt/test/lowfat/{memcpy_oob_recover.cpp => memcpy_oob_static_recover.cpp} (100%)
 create mode 100644 compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
 create mode 100644 compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
 rename compiler-rt/test/lowfat/{memset_oob_fatal.cpp => memset_oob_static_fatal.cpp} (100%)
 rename compiler-rt/test/lowfat/{memset_oob_recover.cpp => memset_oob_static_recover.cpp} (62%)

diff --git a/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
new file mode 100644
index 0000000000000..7e5d76df38dc6
--- /dev/null
+++ b/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
@@ -0,0 +1,28 @@
+// RUN: %clangxx_lowfat -fno-builtin-memcpy %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// Verify that memcpy writing past the end of a LowFat allocation is detected
+// in fatal mode, even when the size isn't a compile-time constant.
+
+#include <string.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv) {
+  char *dst = (char *)malloc(16);
+  char *src = strdup("ABCDEFGHIJKLMNOPQRS"); // 19 bytes
+  if (!dst || !src) return 1;
+
+  // Use argc to prevent the optimizer from knowing the size at compile time.
+  // This forces a call to libc memcpy instead of llvm.memcpy.
+  size_t size = 16 + argc; // 17
+  
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
+  // CHECK: operation = write
+  // CHECK: size      = 16
+  // CHECK: overflow  = +1
+  memcpy(dst, src, size);
+
+  free(dst);
+  free(src);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/memcpy_oob_fatal.cpp b/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memcpy_oob_fatal.cpp
rename to compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
diff --git a/compiler-rt/test/lowfat/memcpy_oob_recover.cpp b/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memcpy_oob_recover.cpp
rename to compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
diff --git a/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
new file mode 100644
index 0000000000000..9984b2b0bd8a4
--- /dev/null
+++ b/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
@@ -0,0 +1,25 @@
+// RUN: %clangxx_lowfat -fno-builtin-memmove %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// Verify that memmove writing past the end of a LowFat allocation is detected
+
+#include <string.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv) {
+  char *dst = (char *)malloc(16);
+  char *src = dst + 4;
+  if (!dst) return 1;
+
+  // Use argc to prevent the optimizer from knowing the size at compile time.
+  size_t size = 12 + argc; // 13. dst+13 is within 16, but src+13 = dst+17, which is OOB (+1)
+  
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
+  // CHECK: operation = read
+  // CHECK: size      = 16
+  // CHECK: overflow  = +1
+  memmove(dst, src, size);
+
+  free(dst);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
new file mode 100644
index 0000000000000..ea6f6083cdecc
--- /dev/null
+++ b/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
@@ -0,0 +1,27 @@
+// RUN: %clangxx_lowfat -fno-builtin-memset %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// Verify that memset writing past the end of a LowFat allocation is detected
+// in fatal mode, even when the size isn't a compile-time constant.
+
+#include <string.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv) {
+  char *buf = (char *)malloc(16);
+  if (!buf) return 1;
+
+  // Use argc to prevent the optimizer from knowing the size at compile time.
+  // This forces a call to libc memset instead of llvm.memset, which proves
+  // our runtime interceptor handles it.
+  size_t size = 16 + argc; // 17
+  
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
+  // CHECK: operation = write
+  // CHECK: size      = 16
+  // CHECK: overflow  = +1
+  memset(buf, 'A', size);
+
+  free(buf);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/memset_oob_fatal.cpp b/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memset_oob_fatal.cpp
rename to compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
diff --git a/compiler-rt/test/lowfat/memset_oob_recover.cpp b/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
similarity index 62%
rename from compiler-rt/test/lowfat/memset_oob_recover.cpp
rename to compiler-rt/test/lowfat/memset_oob_static_recover.cpp
index 36d9d921fbe2e..9932af6e6f703 100644
--- a/compiler-rt/test/lowfat/memset_oob_recover.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
@@ -12,11 +12,9 @@ int main() {
   char *guard = (char *)malloc(16);
   if (!dst || !guard) return 1;
 
-  // FIXME: memset with a compile-time constant ensures clang emits the llvm.memset
-  // intrinsic, which the LowFat pass instruments. A runtime size would become
-  // a plain libc call that the pass cannot see.
+  // memset of 32 bytes into a 16-byte allocation — overflows by 16 bytes.
   // CHECK: LOWFAT WARNING: out-of-bounds error detected!
-  memset(dst, 0, 32); // 32 bytes into a 16-byte allocation → OOB
+  memset(dst, 0, 32);
 
   // CHECK: after memset
   printf("after memset\n");

>From 7e539bf3091b65156610b700afdea5d8be290931 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 15:11:49 +0800
Subject: [PATCH 29/55] [LowFat] Run tests with 3 optimization levels

---
 compiler-rt/test/lowfat/basic_inbounds.cpp            | 5 ++++-
 compiler-rt/test/lowfat/cross_boundary_oob.cpp        | 5 ++++-
 compiler-rt/test/lowfat/free_list_reuse.cpp           | 5 ++++-
 compiler-rt/test/lowfat/inbounds_no_report.cpp        | 5 ++++-
 compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp  | 5 ++++-
 compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp   | 5 ++++-
 compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp | 5 ++++-
 compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp | 5 ++++-
 compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp  | 5 ++++-
 compiler-rt/test/lowfat/memset_oob_static_fatal.cpp   | 5 ++++-
 compiler-rt/test/lowfat/memset_oob_static_recover.cpp | 5 ++++-
 compiler-rt/test/lowfat/oob_read_fatal.cpp            | 5 ++++-
 12 files changed, 48 insertions(+), 12 deletions(-)

diff --git a/compiler-rt/test/lowfat/basic_inbounds.cpp b/compiler-rt/test/lowfat/basic_inbounds.cpp
index 9184c025878f6..6ea912984b57a 100644
--- a/compiler-rt/test/lowfat/basic_inbounds.cpp
+++ b/compiler-rt/test/lowfat/basic_inbounds.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat %s -o %t
+// RUN: %clangxx_lowfat -O0 %s -o %t
+// RUN: %clangxx_lowfat -O1 %s -o %t
+// RUN: %clangxx_lowfat -O2 %s -o %t
+// RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
 // Verify that the LowFat runtime initializes and basic in-bounds
diff --git a/compiler-rt/test/lowfat/cross_boundary_oob.cpp b/compiler-rt/test/lowfat/cross_boundary_oob.cpp
index 3f1ec753b7efd..46f428f3a95f7 100644
--- a/compiler-rt/test/lowfat/cross_boundary_oob.cpp
+++ b/compiler-rt/test/lowfat/cross_boundary_oob.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat %s -o %t
+// RUN: %clangxx_lowfat -O0 %s -o %t
+// RUN: %clangxx_lowfat -O1 %s -o %t
+// RUN: %clangxx_lowfat -O2 %s -o %t
+// RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
 // Verify that a cross-boundary out-of-bounds access is detected.
diff --git a/compiler-rt/test/lowfat/free_list_reuse.cpp b/compiler-rt/test/lowfat/free_list_reuse.cpp
index 27ab57e000eb1..2384eab006362 100644
--- a/compiler-rt/test/lowfat/free_list_reuse.cpp
+++ b/compiler-rt/test/lowfat/free_list_reuse.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat %s -o %t
+// RUN: %clangxx_lowfat -O0 %s -o %t
+// RUN: %clangxx_lowfat -O1 %s -o %t
+// RUN: %clangxx_lowfat -O2 %s -o %t
+// RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
 // Verify that allocation and free list reuse works correctly, with no OOB errors.
diff --git a/compiler-rt/test/lowfat/inbounds_no_report.cpp b/compiler-rt/test/lowfat/inbounds_no_report.cpp
index 0c4863e26a87d..72b0fb07c63ec 100644
--- a/compiler-rt/test/lowfat/inbounds_no_report.cpp
+++ b/compiler-rt/test/lowfat/inbounds_no_report.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat %s -o %t
+// RUN: %clangxx_lowfat -O0 %s -o %t
+// RUN: %clangxx_lowfat -O1 %s -o %t
+// RUN: %clangxx_lowfat -O2 %s -o %t
+// RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
 // Verify that purely in-bounds accesses — including memcpy and memset within
diff --git a/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
index 7e5d76df38dc6..db8c65f25b918 100644
--- a/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
+++ b/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat -fno-builtin-memcpy %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memcpy -O0 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memcpy -O1 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memcpy -O2 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memcpy -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
 // Verify that memcpy writing past the end of a LowFat allocation is detected
diff --git a/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp b/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
index 23f29460211fb..1555fbe70bf4c 100644
--- a/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
+++ b/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat %s -o %t
+// RUN: %clangxx_lowfat -O0 %s -o %t
+// RUN: %clangxx_lowfat -O1 %s -o %t
+// RUN: %clangxx_lowfat -O2 %s -o %t
+// RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
 // Verify that memcpy writing past the end of a LowFat allocation is detected
diff --git a/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp b/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
index 8257a6b1db46a..5a6b0ac647af0 100644
--- a/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
+++ b/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat_recover %s -o %t
+// RUN: %clangxx_lowfat_recover -O0 %s -o %t
+// RUN: %clangxx_lowfat_recover -O1 %s -o %t
+// RUN: %clangxx_lowfat_recover -O2 %s -o %t
+// RUN: %clangxx_lowfat_recover -O3 %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
 // Verify that memcpy OOB in recover mode:
diff --git a/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
index 9984b2b0bd8a4..43b23a8ffca20 100644
--- a/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
+++ b/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat -fno-builtin-memmove %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memmove -O0 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memmove -O1 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memmove -O2 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memmove -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
 // Verify that memmove writing past the end of a LowFat allocation is detected
diff --git a/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
index ea6f6083cdecc..2f78d67e7577b 100644
--- a/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat -fno-builtin-memset %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memset -O0 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memset -O1 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memset -O2 %s -o %t
+// RUN: %clangxx_lowfat -fno-builtin-memset -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
 // Verify that memset writing past the end of a LowFat allocation is detected
diff --git a/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp b/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
index aaa511dbfcb5d..28e72de61f4b5 100644
--- a/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat %s -o %t
+// RUN: %clangxx_lowfat -O0 %s -o %t
+// RUN: %clangxx_lowfat -O1 %s -o %t
+// RUN: %clangxx_lowfat -O2 %s -o %t
+// RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
 // Verify that memset writing past the end of a LowFat allocation is detected
diff --git a/compiler-rt/test/lowfat/memset_oob_static_recover.cpp b/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
index 9932af6e6f703..ad4fbee318e13 100644
--- a/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat_recover %s -o %t
+// RUN: %clangxx_lowfat_recover -O0 %s -o %t
+// RUN: %clangxx_lowfat_recover -O1 %s -o %t
+// RUN: %clangxx_lowfat_recover -O2 %s -o %t
+// RUN: %clangxx_lowfat_recover -O3 %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
 // Verify that memset OOB in recover mode warns and continues.
diff --git a/compiler-rt/test/lowfat/oob_read_fatal.cpp b/compiler-rt/test/lowfat/oob_read_fatal.cpp
index 6b8c33e4a0ab1..38d67e1ecbf0c 100644
--- a/compiler-rt/test/lowfat/oob_read_fatal.cpp
+++ b/compiler-rt/test/lowfat/oob_read_fatal.cpp
@@ -1,4 +1,7 @@
-// RUN: %clangxx_lowfat %s -o %t
+// RUN: %clangxx_lowfat -O0 %s -o %t
+// RUN: %clangxx_lowfat -O1 %s -o %t
+// RUN: %clangxx_lowfat -O2 %s -o %t
+// RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
 // Verify that an OOB load (scalar read across allocation boundary) is detected

>From fff71f47b1394d065a1ca0a1f97b8efc96ae7cb1 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 3 Mar 2026 15:13:25 +0800
Subject: [PATCH 30/55] [LowFat] Add libc interceptors for memset, memcopy and
 memmove

---
 compiler-rt/lib/lowfat/lf_interceptors.cpp | 38 +++++++++++++++++++---
 1 file changed, 33 insertions(+), 5 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_interceptors.cpp b/compiler-rt/lib/lowfat/lf_interceptors.cpp
index 8d96b8d88ef0b..67186a8f28f9d 100644
--- a/compiler-rt/lib/lowfat/lf_interceptors.cpp
+++ b/compiler-rt/lib/lowfat/lf_interceptors.cpp
@@ -14,6 +14,7 @@
 #include "interception/interception.h"
 #include "lf_allocator.h"
 #include "lf_config.h"
+#include "lf_interface.h"
 #include "sanitizer_common/sanitizer_allocator_dlsym.h"
 
 using namespace __sanitizer;
@@ -143,6 +144,35 @@ INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) {
   return REAL(posix_memalign)(memptr, alignment, size);
 }
 
+static inline void check_bounds(const void *ptr, uptr access_size, int is_write) {
+  if (!ptr || access_size == 0) return;
+  if (!__lowfat::CheckBounds((uptr)ptr, access_size)) {
+    uptr start = (uptr)ptr;
+    uptr size = __lowfat::GetSize(start);
+    uptr base = __lowfat::GetBase(start);
+    __lf_report_oob(start + access_size, base, size, is_write);
+  }
+}
+
+// The compiler pass instruments direct memory accesses inline, but cannot 
+// instrument external libc calls. We intercept them here to check bounds.
+INTERCEPTOR(void *, memset, void *dst, int v, uptr size) {
+  check_bounds(dst, size, 1 /* write */);
+  return REAL(memset)(dst, v, size);
+}
+
+INTERCEPTOR(void *, memcpy, void *dst, const void *src, uptr size) {
+  check_bounds(dst, size, 1 /* write */);
+  check_bounds(src, size, 0 /* read */);
+  return REAL(memcpy)(dst, src, size);
+}
+
+INTERCEPTOR(void *, memmove, void *dst, const void *src, uptr size) {
+  check_bounds(dst, size, 1 /* write */);
+  check_bounds(src, size, 0 /* read */);
+  return REAL(memmove)(dst, src, size);
+}
+
 #if SANITIZER_APPLE
 INTERCEPTOR(uptr, malloc_size, void *ptr) {
   if (DlsymAlloc::PointerIsMine(ptr))
@@ -153,10 +183,6 @@ INTERCEPTOR(uptr, malloc_size, void *ptr) {
 }
 #endif
 
-//===----------------------------------------------------------------------===//
-// Initialization
-//===----------------------------------------------------------------------===//
-
 namespace __lowfat {
 void InitializeInterceptors() {
   static int inited = 0;
@@ -168,10 +194,12 @@ void InitializeInterceptors() {
   INTERCEPT_FUNCTION(realloc);
   INTERCEPT_FUNCTION(valloc);
   INTERCEPT_FUNCTION(posix_memalign);
+  INTERCEPT_FUNCTION(memset);
+  INTERCEPT_FUNCTION(memcpy);
+  INTERCEPT_FUNCTION(memmove);
 #if SANITIZER_APPLE
   INTERCEPT_FUNCTION(malloc_size);
 #endif
-
   inited = 1;
 }
 } // namespace __lowfat

>From f2cace424d8599550a5b8971c7b966554e035b64 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 4 Mar 2026 09:52:34 +0800
Subject: [PATCH 31/55] [LowFat] Create Safe to combat DAE and DCE

---
 clang/lib/CodeGen/BackendUtil.cpp             |  36 ++-
 compiler-rt/lib/lowfat/lf_interface.h         |   5 +
 .../Instrumentation/LowFatSanitizer.h         |   8 +
 .../Instrumentation/LowFatSanitizer.cpp       | 268 +++++++-----------
 4 files changed, 147 insertions(+), 170 deletions(-)

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 2dad552af2928..2aff1a03c6e5d 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -108,6 +108,15 @@ static cl::opt<bool> ClSanitizeOnOptimizerEarlyEP(
     "sanitizer-early-opt-ep", cl::Optional,
     cl::desc("Insert sanitizers on OptimizerEarlyEP."));
 
+static cl::opt<LowFatSanitizerOptions::LowFatMode> LowFatMode(
+    "lowfat-mode", cl::init(LowFatSanitizerOptions::LowFatMode::Fast),
+    cl::desc("Controls the placement and strictness of the LowFat pass"),
+    cl::values(
+        clEnumValN(LowFatSanitizerOptions::LowFatMode::Fast, "fast",
+                   "Instrument at OptimizerLastEP (least overhead)"),
+        clEnumValN(LowFatSanitizerOptions::LowFatMode::Safe, "safe",
+                   "Barrier at PipelineStartEP + instrument at OptimizerLastEP")));
+
 // Experiment to mark cold functions as optsize/minsize/optnone.
 // TODO: remove once this is exposed as a proper driver flag.
 static cl::opt<PGOOptions::ColdFuncOpt> ClPGOColdFuncAttr(
@@ -768,7 +777,12 @@ static void addSanitizers(const Triple &TargetTriple,
       MPM.addPass(DataFlowSanitizerPass(LangOpts.NoSanitizeFiles,
                                         PB.getVirtualFileSystemPtr()));
     }
-  // TODO: move LowFat sanitizer back here.
+    if (LangOpts.Sanitize.has(SanitizerKind::LowFat)) {
+      LowFatSanitizerOptions LFOpts;
+      LFOpts.Recover = CodeGenOpts.SanitizeRecover.has(SanitizerKind::LowFat);
+      LFOpts.Mode = LowFatMode;
+      MPM.addPass(LowFatSanitizerPass(LFOpts));
+    }
   };
   if (ClSanitizeOnOptimizerEarlyEP) {
     PB.registerOptimizerEarlyEPCallback(
@@ -787,17 +801,21 @@ static void addSanitizers(const Triple &TargetTriple,
     PB.registerOptimizerLastEPCallback(SanitizersCallback);
   }
 
-  // LowFat must run BEFORE any optimization or attribute inference so that the
-  // memory accesses it needs to instrument are not eliminated by DCE/DSE first.
-  // OptimizerEarlyEP is too late (InferFunctionAttrs already ran); use
-  // PipelineStart which fires before any analysis or optimization pass.
   if (LangOpts.Sanitize.has(SanitizerKind::LowFat)) {
     LowFatSanitizerOptions LFOpts;
     LFOpts.Recover = CodeGenOpts.SanitizeRecover.has(SanitizerKind::LowFat);
-    PB.registerPipelineStartEPCallback(
-        [LFOpts](ModulePassManager &MPM, OptimizationLevel) {
-          MPM.addPass(LowFatSanitizerPass(LFOpts));
-        });
+    LFOpts.Mode = LowFatMode;
+
+    if (LFOpts.Mode == LowFatSanitizerOptions::LowFatMode::Safe) {
+      // Safe: insert barrier + fake.use at PipelineStartEP to preserve loads
+      // through Dead Argument Elimination, then instrument at OptimizerLastEP.
+      LowFatSanitizerOptions BarrierOpts = LFOpts;
+      BarrierOpts.InternalBarrierOnly_ = true;
+      PB.registerPipelineStartEPCallback(
+          [BarrierOpts](ModulePassManager &MPM, OptimizationLevel) {
+            MPM.addPass(LowFatSanitizerPass(BarrierOpts));
+          });
+    }
   }
 }
 
diff --git a/compiler-rt/lib/lowfat/lf_interface.h b/compiler-rt/lib/lowfat/lf_interface.h
index d21cf9062e3b3..63577acf7d782 100644
--- a/compiler-rt/lib/lowfat/lf_interface.h
+++ b/compiler-rt/lib/lowfat/lf_interface.h
@@ -47,6 +47,11 @@ SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_base(uptr ptr);
 // Returns the allocation size, or 0 if the pointer is not within a LowFat region.
 SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_size(uptr ptr);
 
+// Allocate/Deallocate from LowFat regions.
+SANITIZER_INTERFACE_ATTRIBUTE void *__lf_malloc(uptr size);
+SANITIZER_INTERFACE_ATTRIBUTE void __lf_free(void *ptr);
+
+
 }  // extern "C"
 
 #endif  // LF_INTERFACE_H
diff --git a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
index 3509937334423..2403aa9da2430 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
@@ -15,6 +15,14 @@ class Module;
 
 struct LowFatSanitizerOptions {
   bool Recover = false;
+
+  enum class LowFatMode {
+    Fast, /// instrument at OptimizerLastEP
+    Safe, /// Barrier at PipelineStartEP + instrument at OptimizerLastEP
+  };
+  LowFatMode Mode = LowFatMode::Fast;
+
+  bool InternalBarrierOnly_ = false;
 };
 
 class LowFatSanitizerPass : public PassInfoMixin<LowFatSanitizerPass> {
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 0f6d5d300e13e..94587b5435eaf 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -19,12 +19,13 @@
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/IRBuilder.h"
-#include "llvm/IR/InstIterator.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/Module.h"
 #include "llvm/IR/Type.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Support/ModRef.h"
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
 
 using namespace llvm;
@@ -48,39 +49,29 @@ class LowFatSanitizer {
   bool run();
 
 private:
-  /// Get or create the declaration for __lf_report_oob (fatal).
-  FunctionCallee getReportOobFn();
+  Module &M;
+  const LowFatSanitizerOptions &Options;
+  const DataLayout &DL;
+  Type *IntptrTy;
+
+  FunctionCallee ReportOobFn = nullptr;
+  FunctionCallee WarnOobFn = nullptr;
 
-  /// Get or create the declaration for __lf_warn_oob (non-fatal).
+  FunctionCallee getReportOobFn();
   FunctionCallee getWarnOobFn();
 
-  /// Instrument a single function.
   bool instrumentFunction(Function &F);
-
-  /// Instrument a load/store/atomic with a compile-time-known access size.
-  /// Returns true if instrumentation was inserted.
   bool instrumentMemoryAccess(Instruction *I, Value *Ptr, Type *AccessTy);
-
-  /// Instrument a mem intrinsic (memcpy/memset/memmove) with a runtime size.
-  /// Ptr is the pointer to check, SizeVal is the runtime length, IsWrite
-  /// indicates the direction. Returns true if instrumentation was inserted.
-  bool instrumentMemoryRange(Instruction *I, Value *Ptr, Value *SizeVal,
+  bool instrumentMemoryRange(Instruction *I, Value *Ptr, Value *Size,
                              bool IsWrite);
 
-  Module &M;
-  const LowFatSanitizerOptions &Options;
-  const DataLayout &DL;
-  Type *IntptrTy;
-  FunctionCallee ReportOobFn;
-  FunctionCallee WarnOobFn;
+  // Constants (must match lf_config.h)
+  static constexpr uint64_t RegionBase = 0x100000000000ULL;
+  static constexpr uint64_t RegionSizeLog = 32;
+  static constexpr uint64_t NumSizeClasses = 27; // kMaxSizeLog(30) - kMinSizeLog(4) + 1
+  static constexpr uint64_t MinSizeLog = 4;
 };
 
-// LowFat Constants (must match lf_config.h)
-static const uint64_t RegionBase = 0x100000000000ULL;
-static const uint64_t RegionSizeLog = 32;
-static const uint64_t MinSizeLog = 4;
-static const uint64_t NumSizeClasses = 27;
-
 FunctionCallee LowFatSanitizer::getReportOobFn() {
   if (!ReportOobFn) {
     // void __lf_report_oob(uptr ptr, uptr base, uptr size, i8 is_write)
@@ -89,8 +80,12 @@ FunctionCallee LowFatSanitizer::getReportOobFn() {
     ReportOobFn = M.getOrInsertFunction(
         "__lf_report_oob",
         FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy, I8Ty}, false));
-    if (auto *F = dyn_cast<Function>(ReportOobFn.getCallee()))
+    if (auto *F = dyn_cast<Function>(ReportOobFn.getCallee())) {
       F->addFnAttr(Attribute::NoReturn);
+      // inaccessibleMemOnly: prevents the branch from being eliminated
+      // as "dead" if the optimizer can't prove OOB is impossible.
+      F->setMemoryEffects(MemoryEffects::inaccessibleMemOnly());
+    }
   }
   return ReportOobFn;
 }
@@ -103,24 +98,21 @@ FunctionCallee LowFatSanitizer::getWarnOobFn() {
     WarnOobFn = M.getOrInsertFunction(
         "__lf_warn_oob",
         FunctionType::get(VoidTy, {IntptrTy, IntptrTy, IntptrTy, I8Ty}, false));
-    if (auto *F = dyn_cast<Function>(WarnOobFn.getCallee()))
+    if (auto *F = dyn_cast<Function>(WarnOobFn.getCallee())) {
       F->addFnAttr(Attribute::NoUnwind);
+      F->setMemoryEffects(MemoryEffects::inaccessibleMemOnly());
+    }
   }
   return WarnOobFn;
 }
 
 bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
                                               Type *AccessTy) {
-  // Skip if the access type size is not known at compile time
   TypeSize AccessSize = DL.getTypeStoreSize(AccessTy);
-  if (AccessSize.isScalable()) {
-    LLVM_DEBUG(dbgs() << "[LowFat] Skipping scalable type access\n");
+  if (AccessSize.isScalable())
     return false;
-  }
 
   IRBuilder<> IRB(I);
-
-  // Convert pointer to integer
   Value *PtrInt = IRB.CreatePtrToInt(Ptr, IntptrTy);
 
   // 1. Get region index: (Ptr - RegionBase) >> RegionSizeLog
@@ -132,39 +124,27 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
   Value *IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
 
-  // Split block for the slow path (if LowFat)
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
   IRBuilder<> ThenIRB(ThenTerm);
 
   // 3. Compute bounds inside the 'then' block
-  // Size = 1 << (Region + MinSizeLog)
   Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
   Value *ShiftAmount = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
   Value *SizeOne = ConstantInt::get(IntptrTy, 1);
   Value *AllocSize = ThenIRB.CreateShl(SizeOne, ShiftAmount);
-
-  // Mask = ~(AllocSize - 1)
   Value *SizeMinusOne = ThenIRB.CreateSub(AllocSize, SizeOne);
   Value *Mask = ThenIRB.CreateNot(SizeMinusOne);
-
-  // Base = Ptr & Mask
   Value *Base = ThenIRB.CreateAnd(PtrInt, Mask);
-
-  // End = Base + AllocSize
   Value *End = ThenIRB.CreateAdd(Base, AllocSize);
 
   // 4. Check access: Ptr + AccessSize <= End
-  // Equivalent OOB check: (Ptr + AccessSize) > End
   Value *AccessSizeVal = ConstantInt::get(IntptrTy, AccessSize.getFixedValue());
   Value *AccessEnd = ThenIRB.CreateAdd(PtrInt, AccessSizeVal);
   Value *IsOOB = ThenIRB.CreateICmpUGT(AccessEnd, End);
 
-  // Split again for OOB reporting
   Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
   IRBuilder<> OobIRB(OobTerm);
   
-  // 5. Report OOB (slow path) — pass is_write so the runtime can print
-  // "read" or "write" in the diagnostic.
   FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
   Type *I8Ty = Type::getInt8Ty(M.getContext());
   bool IsWrite = isa<StoreInst>(I) || isa<AtomicRMWInst>(I) ||
@@ -172,174 +152,140 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
   OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
 
-  LLVM_DEBUG(dbgs() << "[LowFat] Instrumented (inline, "
-                    << (Options.Recover ? "recover" : "fatal")
-                    << ", " << (IsWrite ? "write" : "read")
-                    << "): " << *I << "\n");
+  if (isa<LoadInst>(I)) NumInstrumentedLoads++;
+  else if (isa<StoreInst>(I)) NumInstrumentedStores++;
+  else NumInstrumentedAtomics++;
+
   return true;
 }
 
 bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
-                                            Value *SizeVal, bool IsWrite) {
-  // Same bounds-checking logic as instrumentMemoryAccess, but uses a runtime
-  // SizeVal instead of a compile-time constant AccessSize.
+                                             Value *Size, bool IsWrite) {
   IRBuilder<> IRB(I);
-
-  // Cast pointer and size to intptr
   Value *PtrInt = IRB.CreatePtrToInt(Ptr, IntptrTy);
-  // Zero-extend SizeVal to IntptrTy if needed (len may be i32 or i64)
-  Value *Size = IRB.CreateZExtOrTrunc(SizeVal, IntptrTy);
+  Value *SizeInt = IRB.CreateZExtOrTrunc(Size, IntptrTy);
 
-  // 1. Get region index: (Ptr - RegionBase) >> RegionSizeLog
   Value *RegionBaseVal = ConstantInt::get(IntptrTy, RegionBase);
   Value *RegionOffset = IRB.CreateSub(PtrInt, RegionBaseVal);
   Value *RegionIndex = IRB.CreateLShr(RegionOffset, RegionSizeLog);
 
-  // 2. Check if LowFat pointer: Region < NumSizeClasses
   Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
   Value *IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
 
-  // Split block for the LowFat slow path
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
   IRBuilder<> ThenIRB(ThenTerm);
 
-  // 3. Compute allocation size from region index: 1 << (region + MinSizeLog)
   Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
   Value *ShiftAmount = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
   Value *SizeOne = ConstantInt::get(IntptrTy, 1);
   Value *AllocSize = ThenIRB.CreateShl(SizeOne, ShiftAmount);
-
-  // 4. Compute base: Ptr & ~(AllocSize - 1)
   Value *SizeMinusOne = ThenIRB.CreateSub(AllocSize, SizeOne);
   Value *Mask = ThenIRB.CreateNot(SizeMinusOne);
   Value *Base = ThenIRB.CreateAnd(PtrInt, Mask);
+  Value *End = ThenIRB.CreateAdd(Base, AllocSize);
 
-  // 5. Compute end of allocation and end of access range
-  Value *AllocEnd = ThenIRB.CreateAdd(Base, AllocSize);
-  Value *AccessEnd = ThenIRB.CreateAdd(PtrInt, Size);
-
-  // 6. OOB if access end exceeds allocation end
-  Value *IsOOB = ThenIRB.CreateICmpUGT(AccessEnd, AllocEnd);
+  Value *AccessEnd = ThenIRB.CreateAdd(PtrInt, SizeInt);
+  Value *IsOOB = ThenIRB.CreateICmpUGT(AccessEnd, End);
 
-  // 7. Report OOB
   Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
   IRBuilder<> OobIRB(OobTerm);
-
+  
   FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
   Type *I8Ty = Type::getInt8Ty(M.getContext());
   Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
-  OobIRB.CreateCall(OobFn, {AccessEnd, Base, AllocSize, IsWriteVal});
+  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
 
-  LLVM_DEBUG(dbgs() << "[LowFat] Instrumented mem intrinsic ("
-                    << (Options.Recover ? "recover" : "fatal")
-                    << ", " << (IsWrite ? "write" : "read")
-                    << "): " << *I << "\n");
+  NumInstrumentedMemIntrinsics++;
   return true;
 }
 
 bool LowFatSanitizer::instrumentFunction(Function &F) {
-  // Skip functions that shouldn't be instrumented
-  if (F.isDeclaration())
-    return false;
-
-  // Skip the runtime library functions themselves
-  if (F.getName().starts_with("__lf_"))
-    return false;
-
-  // Skip functions with nosanitize attribute
-  if (F.hasFnAttribute(Attribute::NoSanitizeBounds))
-    return false;
-
-  LLVM_DEBUG(dbgs() << "[LowFat] Instrumenting function: " << F.getName() << "\n");
-
   bool Modified = false;
-
-  // Track two kinds of instrumentation targets:
-  //   1. Load/store/atomic: compile-time access size, from instruction type
-  //   2. Mem intrinsics: runtime access size (the 'len' argument)
-  SmallVector<std::pair<Instruction *, std::pair<Value *, Type *>>, 16> ToInstrument;
-
-  // Each MemRange entry is {I, Ptr, SizeVal, IsWrite}
-  struct MemRange { Instruction *I; Value *Ptr; Value *Size; bool IsWrite; };
-  SmallVector<MemRange, 8> MemRanges;
-
-  for (Instruction &I : instructions(F)) {
-    Value *Ptr = nullptr;
-    Type *AccessTy = nullptr;
-
-    if (auto *LI = dyn_cast<LoadInst>(&I)) {
-      if (!LI->isVolatile()) {
-        Ptr = LI->getPointerOperand();
-        AccessTy = LI->getType();
-      }
-    } else if (auto *SI = dyn_cast<StoreInst>(&I)) {
-      if (!SI->isVolatile()) {
-        Ptr = SI->getPointerOperand();
-        AccessTy = SI->getValueOperand()->getType();
-      }
-    } else if (auto *AI = dyn_cast<AtomicRMWInst>(&I)) {
-      if (!AI->isVolatile()) {
-        Ptr = AI->getPointerOperand();
-        AccessTy = AI->getValOperand()->getType();
-      }
-    } else if (auto *AI = dyn_cast<AtomicCmpXchgInst>(&I)) {
-      if (!AI->isVolatile()) {
-        Ptr = AI->getPointerOperand();
-        AccessTy = AI->getCompareOperand()->getType();
-      }
-    } else if (auto *MI = dyn_cast<MemSetInst>(&I)) {
-      // memset(dst, val, len) — write-range check on dst
-      if (!MI->isVolatile())
-        MemRanges.push_back({&I, MI->getDest(), MI->getLength(), /*IsWrite=*/true});
-    } else if (auto *MI = dyn_cast<MemTransferInst>(&I)) {
-      // memcpy/memmove(dst, src, len) — write dst, read src
-      if (!MI->isVolatile()) {
-        MemRanges.push_back({&I, MI->getDest(), MI->getLength(), /*IsWrite=*/true});
-        MemRanges.push_back({&I, MI->getSource(), MI->getLength(), /*IsWrite=*/false});
-      }
-    }
-
-    if (Ptr && AccessTy)
-      ToInstrument.push_back({&I, {Ptr, AccessTy}});
-  }
-
-  // Instrument load/store/atomics
-  for (auto &Entry : ToInstrument) {
-    Instruction *I = Entry.first;
-    Value *Ptr = Entry.second.first;
-    Type *AccessTy = Entry.second.second;
-
-    if (instrumentMemoryAccess(I, Ptr, AccessTy)) {
-      Modified = true;
-      if (isa<LoadInst>(I))
-        ++NumInstrumentedLoads;
-      else if (isa<StoreInst>(I))
-        ++NumInstrumentedStores;
-      else
-        ++NumInstrumentedAtomics;
+  SmallVector<Instruction *, 16> ToInstrument;
+
+  for (auto &BB : F) {
+    for (auto &I : BB) {
+      if (isa<LoadInst>(&I) || isa<StoreInst>(&I) || isa<AtomicRMWInst>(&I) ||
+          isa<AtomicCmpXchgInst>(&I))
+        ToInstrument.push_back(&I);
+      else if (isa<MemIntrinsic>(&I))
+        ToInstrument.push_back(&I);
     }
   }
 
-  // Instrument mem intrinsics
-  for (auto &MR : MemRanges) {
-    if (instrumentMemoryRange(MR.I, MR.Ptr, MR.Size, MR.IsWrite)) {
-      Modified = true;
-      ++NumInstrumentedMemIntrinsics;
+  for (Instruction *I : ToInstrument) {
+    if (auto *LI = dyn_cast<LoadInst>(I))
+      Modified |= instrumentMemoryAccess(I, LI->getPointerOperand(), LI->getType());
+    else if (auto *SI = dyn_cast<StoreInst>(I))
+      Modified |= instrumentMemoryAccess(I, SI->getPointerOperand(), SI->getValueOperand()->getType());
+    else if (auto *RMW = dyn_cast<AtomicRMWInst>(I))
+      Modified |= instrumentMemoryAccess(I, RMW->getPointerOperand(), RMW->getValOperand()->getType());
+    else if (auto *CmpXchg = dyn_cast<AtomicCmpXchgInst>(I))
+      Modified |= instrumentMemoryAccess(I, CmpXchg->getPointerOperand(), CmpXchg->getNewValOperand()->getType());
+    else if (auto *MS = dyn_cast<MemSetInst>(I))
+      Modified |= instrumentMemoryRange(I, MS->getDest(), MS->getLength(), true);
+    else if (auto *MT = dyn_cast<MemTransferInst>(I)) {
+      Modified |= instrumentMemoryRange(I, MT->getDest(), MT->getLength(), true);
+      Modified |= instrumentMemoryRange(I, MT->getSource(), MT->getLength(), false);
     }
   }
-
   return Modified;
 }
 
 bool LowFatSanitizer::run() {
+  LLVM_DEBUG(dbgs() << "[LowFat] run() Mode=" << (int)Options.Mode
+                   << " BarrierOnly=" << Options.InternalBarrierOnly_ << "\n");
   LLVM_DEBUG(dbgs() << "[LowFat] Running on module: " << M.getName() << "\n");
 
-  bool Modified = false;
+  // Safe mode: InternalBarrierOnly_ is set for the PipelineStartEP pass.
+  if (Options.InternalBarrierOnly_) {
+    LLVM_DEBUG(dbgs() << "[LowFat] Inserting barriers (Safe mode)\n");
+    bool Modified = false;
+    // Declare llvm.sideeffect and llvm.fake.use once for the module.
+    Function *SideEffectFn =
+        Intrinsic::getOrInsertDeclaration(&M, Intrinsic::sideeffect);
+    Function *FakeUseFn =
+        Intrinsic::getOrInsertDeclaration(&M, Intrinsic::fake_use);
+    for (Function &F : M) {
+      if (F.isDeclaration() || F.empty())
+        continue;
+
+      // Insert @llvm.sideeffect() at function entry to prevent FunctionAttrs
+      // from inferring memory(none) on callers, blocking call-level DCE.
+      IRBuilder<> IRB(&*F.getEntryBlock().getFirstInsertionPt());
+      IRB.CreateCall(SideEffectFn, {});
+      LLVM_DEBUG(dbgs() << "    [LowFat] Inserted sideeffect barrier in: "
+                       << F.getName() << "\n");
+
+      // Insert @llvm.fake.use(loaded_val) immediately after every load.
+      // Without this, Dead Argument Elimination (DAE) can prove that a
+      // function's return value is unused at all call sites and rewrite
+      //   ret %loaded_val  →  ret undef
+      // making the load itself dead, which is then DCE'd before the LowFat
+      // pass at OptimizerLastEP ever sees it.
+      SmallVector<LoadInst *, 8> Loads;
+      for (BasicBlock &BB : F)
+        for (Instruction &I : BB)
+          if (auto *LI = dyn_cast<LoadInst>(&I))
+            Loads.push_back(LI);
+      for (LoadInst *LI : Loads) {
+        IRBuilder<> LIRB(LI->getNextNode());
+        LIRB.CreateCall(FakeUseFn, {LI});
+        LLVM_DEBUG(dbgs() << "    [LowFat] Inserted fake.use for load in: "
+                         << F.getName() << "\n");
+      }
+
+      Modified = true;
+    }
+    return Modified;
+  }
 
+  bool Modified = false;
   for (Function &F : M) {
+    if (F.isDeclaration() || F.empty())
+      continue;
     Modified |= instrumentFunction(F);
   }
-
   return Modified;
 }
 

>From fed9bd9c19efd0cc289324a9e0d29c8860d0ad44 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 4 Mar 2026 09:53:57 +0800
Subject: [PATCH 32/55] [LowFat] Fix tests to run on all optimizations

---
 compiler-rt/test/lowfat/lit.cfg.py            | 27 ++++++++++++++-----
 .../test/lowfat/memcpy_oob_static_fatal.cpp   |  7 ++---
 .../test/lowfat/memcpy_oob_static_recover.cpp |  9 +++----
 .../test/lowfat/memset_oob_static_fatal.cpp   |  7 ++---
 .../test/lowfat/memset_oob_static_recover.cpp |  9 +++----
 compiler-rt/test/lowfat/oob_read_fatal.cpp    | 12 ++++-----
 6 files changed, 38 insertions(+), 33 deletions(-)

diff --git a/compiler-rt/test/lowfat/lit.cfg.py b/compiler-rt/test/lowfat/lit.cfg.py
index 993b5ba162dc1..2c83322d99b94 100644
--- a/compiler-rt/test/lowfat/lit.cfg.py
+++ b/compiler-rt/test/lowfat/lit.cfg.py
@@ -38,14 +38,29 @@ def build_invocation(flags):
     return " " + " ".join([clang] + flags) + " "
 
 
-# Fatal mode  : terminate on first OOB
-lowfat_flags = ["-fsanitize=lowfat", "-O1"]
-# Recover mode: warn + continue
-lowfat_recover_flags = lowfat_flags + ["-fsanitize-recover=lowfat"]
+# Base flags
+lowfat_base = ["-fsanitize=lowfat"]
 
-config.substitutions.append(("%clangxx_lowfat ", build_invocation(lowfat_flags)))
+# Fast mode (OptimizerLastEP)
+lowfat_fast = lowfat_base + ["-mllvm", "-lowfat-mode=fast"]
+# Safe mode (Barrier at PipelineStartEP + instrument at OptimizerLastEP)
+lowfat_safe = lowfat_base + ["-mllvm", "-lowfat-mode=safe"]
+
+config.substitutions.append(("%clangxx_lowfat ", build_invocation(lowfat_fast)))
+config.substitutions.append(("%clangxx_lowfat_safe ", build_invocation(lowfat_safe)))
+
+# Recover mode versions
+config.substitutions.append(
+    (
+        "%clangxx_lowfat_recover ",
+        build_invocation(lowfat_fast + ["-fsanitize-recover=lowfat"]),
+    )
+)
 config.substitutions.append(
-    ("%clangxx_lowfat_recover ", build_invocation(lowfat_recover_flags))
+    (
+        "%clangxx_lowfat_safe_recover ",
+        build_invocation(lowfat_safe + ["-fsanitize-recover=lowfat"]),
+    )
 )
 
 # Only Darwin and Linux are supported.
diff --git a/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp b/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
index 1555fbe70bf4c..c95553fc80c2a 100644
--- a/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
+++ b/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
@@ -1,8 +1,5 @@
-// RUN: %clangxx_lowfat -O0 %s -o %t
-// RUN: %clangxx_lowfat -O1 %s -o %t
-// RUN: %clangxx_lowfat -O2 %s -o %t
-// RUN: %clangxx_lowfat -O3 %s -o %t
-// RUN: not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
 
 // Verify that memcpy writing past the end of a LowFat allocation is detected
 // in fatal mode.  The process must exit with a non-zero code (checked by
diff --git a/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp b/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
index 5a6b0ac647af0..62316bf1a1a0e 100644
--- a/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
+++ b/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
@@ -1,8 +1,7 @@
-// RUN: %clangxx_lowfat_recover -O0 %s -o %t
-// RUN: %clangxx_lowfat_recover -O1 %s -o %t
-// RUN: %clangxx_lowfat_recover -O2 %s -o %t
-// RUN: %clangxx_lowfat_recover -O3 %s -o %t
-// RUN: %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe_recover -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe_recover -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe_recover -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe_recover -O3 %s -o %t && %run %t 2>&1 | FileCheck %s
 
 // Verify that memcpy OOB in recover mode:
 //   1. Prints a WARNING (not ERROR).
diff --git a/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp b/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
index 28e72de61f4b5..638c7a57dee07 100644
--- a/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
@@ -1,8 +1,5 @@
-// RUN: %clangxx_lowfat -O0 %s -o %t
-// RUN: %clangxx_lowfat -O1 %s -o %t
-// RUN: %clangxx_lowfat -O2 %s -o %t
-// RUN: %clangxx_lowfat -O3 %s -o %t
-// RUN: not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
 
 // Verify that memset writing past the end of a LowFat allocation is detected
 // in fatal mode.
diff --git a/compiler-rt/test/lowfat/memset_oob_static_recover.cpp b/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
index ad4fbee318e13..c5f3d5b2702c2 100644
--- a/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
@@ -1,8 +1,7 @@
-// RUN: %clangxx_lowfat_recover -O0 %s -o %t
-// RUN: %clangxx_lowfat_recover -O1 %s -o %t
-// RUN: %clangxx_lowfat_recover -O2 %s -o %t
-// RUN: %clangxx_lowfat_recover -O3 %s -o %t
-// RUN: %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe_recover -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe_recover -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe_recover -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe_recover -O3 %s -o %t && %run %t 2>&1 | FileCheck %s
 
 // Verify that memset OOB in recover mode warns and continues.
 
diff --git a/compiler-rt/test/lowfat/oob_read_fatal.cpp b/compiler-rt/test/lowfat/oob_read_fatal.cpp
index 38d67e1ecbf0c..290f450ad626d 100644
--- a/compiler-rt/test/lowfat/oob_read_fatal.cpp
+++ b/compiler-rt/test/lowfat/oob_read_fatal.cpp
@@ -1,11 +1,10 @@
-// RUN: %clangxx_lowfat -O0 %s -o %t
-// RUN: %clangxx_lowfat -O1 %s -o %t
-// RUN: %clangxx_lowfat -O2 %s -o %t
-// RUN: %clangxx_lowfat -O3 %s -o %t
-// RUN: not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s
 
 // Verify that an OOB load (scalar read across allocation boundary) is detected
-// in fatal mode.
+// in fatal mode across all optimisation levels and modes.
 
 #include <cstdlib>
 
@@ -19,7 +18,6 @@ int main() {
   // Read 8 bytes at offset 28 of a 32-byte allocation:
   //   bytes 28–35 exceed the 32-byte boundary → OOB.
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
-  // FIXME: must NOT use volatile here — the LowFat pass skips volatile accesses.
   double *p = (double *)(buf + 28);
   double val = *p; // 8-byte read at offset 28 of 32-byte alloc → OOB (bytes 28–35)
   (void)val;       // keep live to prevent DSE; crash fires on the load above

>From e03580ca0f0a6e38eb1fb9595683e55452636bd5 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 4 Mar 2026 09:54:34 +0800
Subject: [PATCH 33/55] [LowFat] Add tests to differentiate between fast and
 safe mode

---
 compiler-rt/test/lowfat/dead_load.cpp         | 24 +++++++++
 .../lowfat/malloc_intercepted_inbounds.cpp    | 33 ++++++++++++
 .../test/lowfat/mode_baseline_all_detect.cpp  | 27 ++++++++++
 .../test/lowfat/mode_diff_pure_call.cpp       | 52 +++++++++++++++++++
 4 files changed, 136 insertions(+)
 create mode 100644 compiler-rt/test/lowfat/dead_load.cpp
 create mode 100644 compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp
 create mode 100644 compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
 create mode 100644 compiler-rt/test/lowfat/mode_diff_pure_call.cpp

diff --git a/compiler-rt/test/lowfat/dead_load.cpp b/compiler-rt/test/lowfat/dead_load.cpp
new file mode 100644
index 0000000000000..15e30304506cd
--- /dev/null
+++ b/compiler-rt/test/lowfat/dead_load.cpp
@@ -0,0 +1,24 @@
+// RUN: %clangxx_lowfat -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL
+// RUN: %clangxx_lowfat_safe -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL
+
+// Verifies that a genuine OOB heap read is detected in both Fast and Safe mode
+// when the loaded value is actually used (returned and passed to printf).
+
+#include <cstdio>
+#include <cstdlib>
+
+__attribute__((noinline))
+double oob_read(char *p) {
+  // 8-byte read at offset 14 of a 16-byte allocation.
+  // Bytes [14, 22) exceed the slot boundary [0, 16) → genuine OOB.
+  return *(double *)(p + 14);
+}
+
+int main() {
+  char *p = (char *)malloc(16);
+  double val = oob_read(p);  // return value kept live → load not DCE'd
+  // CHECK-ALL: LOWFAT ERROR: out-of-bounds error detected!
+  printf("val=%f\n", val);
+  free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp b/compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp
new file mode 100644
index 0000000000000..f8359b2d3f45c
--- /dev/null
+++ b/compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp
@@ -0,0 +1,33 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+// Verify that malloc-intercepted allocations are correctly handled for in-bounds accesses.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+int main() {
+  // malloc goes through the LowFat interceptor → produces a LowFat pointer.
+  char *buf = (char *)malloc(32);
+  if (!buf) return 1;
+
+  // In-bounds scalar accesses — first and last byte.
+  buf[0] = 'A';
+  buf[31] = 'Z';
+
+  // In-bounds memcpy exactly within the allocation.
+  const char src[32] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+  memcpy(buf, src, 32);
+
+  // In-bounds memset exactly within the allocation.
+  memset(buf, 0, 32);
+
+  free(buf);
+
+  // CHECK: intercepted_inbounds: ok
+  // CHECK-NOT: LOWFAT ERROR
+  // CHECK-NOT: LOWFAT WARNING
+  printf("intercepted_inbounds: ok\n");
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp b/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
new file mode 100644
index 0000000000000..6fdf82f3acaf9
--- /dev/null
+++ b/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
@@ -0,0 +1,27 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OOB
+// RUN: %clangxx_lowfat_safe -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OOB
+// RUN: %clangxx_lowfat -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OOB
+// RUN: %clangxx_lowfat_safe -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OOB
+
+// Baseline: all three modes detect an OOB access whose result IS observable
+// Contrast with mode_diff_pure_call.cpp where a discarded-result call lets
+// Fast eliminate the load before the LowFat pass even sees it.
+
+#include <cstdio>
+#include <cstdlib>
+
+volatile char sink; // volatile global: any write/read here is always observable
+
+int main() {
+  char *p = (char *)malloc(16);
+
+  // 8-byte (double) OOB read at offset 14: bytes [14, 22) overflow the
+  // 16-byte LowFat slot [0, 16). 
+  sink = (char)(*reinterpret_cast<volatile double *>(p + 14));
+
+  free(p);
+
+  // CHECK-OOB: LOWFAT ERROR: out-of-bounds error detected!
+  printf("DONE\n");
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/mode_diff_pure_call.cpp b/compiler-rt/test/lowfat/mode_diff_pure_call.cpp
new file mode 100644
index 0000000000000..5f3109ea81d02
--- /dev/null
+++ b/compiler-rt/test/lowfat/mode_diff_pure_call.cpp
@@ -0,0 +1,52 @@
+// RUN: %clangxx_lowfat -O3 %s -o %t && %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-MISS
+// RUN: %clangxx_lowfat_safe -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-CATCH
+
+// Demonstrates a behavioral difference between Fast mode and Safe/Comprehensive mode.
+//
+// The scenario: a noinline function performs an OOB heap read but its return value
+// is discarded by the caller.
+//
+// Fast mode has no PipelineStartEP pass. LLVM's Dead Argument Elimination (DAE)
+// sees peek()'s return value is unused at all call sites and rewrites:
+//   ret %loaded_val  →  ret undef
+// The load becomes dead and is DCE'd. The LowFat pass at OptimizerLastEP finds
+// no load to instrument — the OOB is missed.
+//
+// Safe mode's barrier pass runs at PipelineStartEP and inserts:
+//   1. @llvm.sideeffect() — prevents call-level DCE by blocking memory(none)
+//      inference on peek().
+//   2. @llvm.fake.use(loaded_val) — after every load. fake.use creates a data
+//      dependency on the loaded value without emitting machine code,
+//      preventing DAE from marking the return value as dead. The load survives
+//      to OptimizerLastEP where LowFat instruments it → OOB is caught.
+//
+// Comprehensive mode instruments at PipelineStartEP before any optimization,
+// so the check call is inserted before DAE has a chance to remove the load.
+//
+// Expected outputs:
+//   Fast (-O3):          load DCE'd by DAE → no OOB check → program exits 0 → DONE
+//   Safe (-O3):          fake.use keeps load alive → OOB detected → LOWFAT ERROR
+
+#include <cstdio>
+#include <cstdlib>
+
+// noinline: the optimizer cannot see the body from the call site in Fast mode,
+// so it performs inter-procedural attribute inference rather than inlining.
+__attribute__((noinline))
+static double peek(char *p) {
+  // 8-byte (double) OOB read. p was allocated with malloc(16); a double starting
+  // at offset 14 spans bytes [14, 22), which overflows the 16-byte LowFat slot
+  // boundary at byte 16. LowFat detects this as an out-of-bounds access.
+  return *reinterpret_cast<double *>(p + 14);
+}
+
+int main() {
+  char *p = (char *)malloc(16);
+  peek(p);   // Return value discarded — caller has no use for it.
+  free(p);
+
+  // CHECK-MISS: DONE
+  // CHECK-CATCH: LOWFAT ERROR: out-of-bounds error detected!
+  printf("DONE\n");
+  return 0;
+}

>From bc095022e0b1cd924f30315d28f8dbc7269d04e3 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 4 Mar 2026 10:58:43 +0800
Subject: [PATCH 34/55] [LowFat] Use recover flag for rtl check_bounds

---
 compiler-rt/lib/lowfat/lf_interceptors.cpp    |  6 ++++-
 compiler-rt/lib/lowfat/lf_interface.h         |  5 ++++
 compiler-rt/lib/lowfat/lf_rtl.cpp             |  9 ++++++++
 .../Instrumentation/LowFatSanitizer.cpp       | 23 +++++++++++++++++++
 4 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/compiler-rt/lib/lowfat/lf_interceptors.cpp b/compiler-rt/lib/lowfat/lf_interceptors.cpp
index 67186a8f28f9d..b857ca845b3ff 100644
--- a/compiler-rt/lib/lowfat/lf_interceptors.cpp
+++ b/compiler-rt/lib/lowfat/lf_interceptors.cpp
@@ -21,6 +21,7 @@ using namespace __sanitizer;
 
 namespace __lowfat {
 extern bool lowfat_inited;
+extern bool lowfat_recover;
 } // namespace __lowfat
 
 // DlsymAlloc handles allocations that happen before our runtime is initialized
@@ -150,7 +151,10 @@ static inline void check_bounds(const void *ptr, uptr access_size, int is_write)
     uptr start = (uptr)ptr;
     uptr size = __lowfat::GetSize(start);
     uptr base = __lowfat::GetBase(start);
-    __lf_report_oob(start + access_size, base, size, is_write);
+    if (__lowfat::lowfat_recover)
+      __lf_warn_oob(start + access_size, base, size, is_write);
+    else
+      __lf_report_oob(start + access_size, base, size, is_write);
   }
 }
 
diff --git a/compiler-rt/lib/lowfat/lf_interface.h b/compiler-rt/lib/lowfat/lf_interface.h
index 63577acf7d782..a3691987ed247 100644
--- a/compiler-rt/lib/lowfat/lf_interface.h
+++ b/compiler-rt/lib/lowfat/lf_interface.h
@@ -30,6 +30,11 @@ SANITIZER_INTERFACE_ATTRIBUTE void __lf_init();
 // Report a fatal out-of-bounds access and terminate.
 // ptr: the offending pointer, base: base of the allocation,
 // bound: size of the allocation, is_write: 1=write 0=read
+
+// Called from a compiler-generated module constructor to communicate
+// -fsanitize-recover=lowfat to the runtime interceptors.
+SANITIZER_INTERFACE_ATTRIBUTE void __lf_set_recover(int recover);
+
 SANITIZER_INTERFACE_ATTRIBUTE void __lf_report_oob(uptr ptr, uptr base,
                                                     uptr bound, int is_write);
 
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 3977445520e28..94c7985569794 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -29,6 +29,10 @@ namespace __lowfat {
 // Flag to track initialization state (not static — accessed by lf_interceptors.cpp)
 bool lowfat_inited = false;
 
+// Set to true when -fsanitize-recover=lowfat is active. Controls whether
+// interceptor-level OOB (memset/memcpy/memmove) warns-and-continues or aborts.
+bool lowfat_recover = false;
+
 // Region table - initialized in __lf_init
 // TODO: not actually needed, to use for convenience
 RegionInfo kRegions[kNumSizeClasses];
@@ -198,6 +202,11 @@ static void PrintWarning(uptr ptr, uptr base, uptr bound, int is_write) {
 
 extern "C" {
 
+SANITIZER_INTERFACE_ATTRIBUTE
+void __lf_set_recover(int recover) {
+  __lowfat::lowfat_recover = (recover != 0);
+}
+
 SANITIZER_INTERFACE_ATTRIBUTE
 void __lf_init() {
   if (__lowfat::lowfat_inited)
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 94587b5435eaf..66170ec3dd367 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -27,6 +27,7 @@
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/ModRef.h"
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
 
 using namespace llvm;
 
@@ -286,6 +287,28 @@ bool LowFatSanitizer::run() {
       continue;
     Modified |= instrumentFunction(F);
   }
+
+  // Emit a module constructor that calls __lf_set_recover(Recover) so the
+  // runtime interceptors (memset/memcpy/memmove) know whether to warn or abort.
+  // This runs before main() via .init_array / __mod_init_func.
+  if (Options.Recover) {
+    LLVMContext &Ctx = M.getContext();
+    FunctionType *SetRecoverTy =
+        FunctionType::get(Type::getVoidTy(Ctx), {Type::getInt32Ty(Ctx)}, false);
+    FunctionCallee SetRecoverFn =
+        M.getOrInsertFunction("__lf_set_recover", SetRecoverTy);
+    Function *Ctor = Function::Create(
+        FunctionType::get(Type::getVoidTy(Ctx), false),
+        GlobalValue::InternalLinkage, "__lowfat_set_recover_ctor", &M);
+    BasicBlock *BB = BasicBlock::Create(Ctx, "entry", Ctor);
+    IRBuilder<> CtorBuilder(BB);
+    CtorBuilder.CreateCall(SetRecoverFn,
+                           {ConstantInt::get(Type::getInt32Ty(Ctx), 1)});
+    CtorBuilder.CreateRetVoid();
+    appendToGlobalCtors(M, Ctor, /*Priority=*/0);
+    Modified = true;
+  }
+
   return Modified;
 }
 

>From d43345c865bad8cfbcd1395127b92854aee8cd6a Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 7 Mar 2026 21:41:05 +0800
Subject: [PATCH 35/55] [LowFat] Enable custom memory config mode

---
 compiler-rt/lib/lowfat/CMakeLists.txt         |  59 ++-
 compiler-rt/lib/lowfat/lf_config.h            |  97 ++++-
 compiler-rt/lib/lowfat/lf_rtl.cpp             |  52 ++-
 compiler-rt/lib/lowfat/tools/lf_config_gen.c  | 357 +++++++++++++++++
 compiler-rt/lib/lowfat/tools/sizes.cfg        |  57 +++
 .../Transforms/Instrumentation/CMakeLists.txt |  42 ++
 .../Instrumentation/LowFatSanitizer.cpp       | 372 +++++++++++++++---
 llvm/runtimes/CMakeLists.txt                  |   6 +
 8 files changed, 969 insertions(+), 73 deletions(-)
 create mode 100644 compiler-rt/lib/lowfat/tools/lf_config_gen.c
 create mode 100644 compiler-rt/lib/lowfat/tools/sizes.cfg

diff --git a/compiler-rt/lib/lowfat/CMakeLists.txt b/compiler-rt/lib/lowfat/CMakeLists.txt
index 22fa2c5463c1e..07e4ffe85af06 100644
--- a/compiler-rt/lib/lowfat/CMakeLists.txt
+++ b/compiler-rt/lib/lowfat/CMakeLists.txt
@@ -1,5 +1,61 @@
 include_directories(..)
 
+set(LOWFAT_COMMON_DEFINITIONS)
+
+#===------------------------------------------------------------------------===
+# Custom size-class configuration (non-POW2 support)
+#
+# Set -DLOWFAT_SIZES_CFG=/path/to/sizes.cfg at cmake time to enable.
+# This builds the lf_config_gen host tool, runs it to produce
+# lf_config_generated.h, and compiles the runtime with LOWFAT_CUSTOM_CONFIG.
+#===------------------------------------------------------------------------===
+set(LOWFAT_SIZES_CFG "" CACHE FILEPATH
+  "Path to sizes.cfg for non-POW2 LowFat size class generation. Empty = POW2-only mode.")
+
+if(LOWFAT_SIZES_CFG)
+  # ---- Build the generator tool (host compiler, no LLVM deps) ----
+  # Use add_custom_target / add_custom_command rather than add_executable so
+  # this host tool is never cross-compiled.
+  set(LOWFAT_CONFIG_GEN_SRC
+      ${CMAKE_CURRENT_SOURCE_DIR}/tools/lf_config_gen.c)
+  set(LOWFAT_CONFIG_GEN_BIN
+      ${CMAKE_CURRENT_BINARY_DIR}/lf_config_gen${CMAKE_EXECUTABLE_SUFFIX})
+  set(LOWFAT_GENERATED_HEADER
+      ${CMAKE_CURRENT_BINARY_DIR}/lf_config_generated.h)
+
+  add_custom_command(
+    OUTPUT  ${LOWFAT_CONFIG_GEN_BIN}
+    COMMAND ${CMAKE_C_COMPILER} -std=c11 -O2
+            ${LOWFAT_CONFIG_GEN_SRC}
+            -o ${LOWFAT_CONFIG_GEN_BIN}
+    DEPENDS ${LOWFAT_CONFIG_GEN_SRC}
+    COMMENT "Compiling lf_config_gen host tool"
+    VERBATIM
+  )
+
+  add_custom_command(
+    OUTPUT  ${LOWFAT_GENERATED_HEADER}
+    COMMAND ${LOWFAT_CONFIG_GEN_BIN} ${LOWFAT_SIZES_CFG} ${LOWFAT_GENERATED_HEADER}
+    DEPENDS ${LOWFAT_CONFIG_GEN_BIN} ${LOWFAT_SIZES_CFG}
+    COMMENT "Generating LowFat size tables from ${LOWFAT_SIZES_CFG}"
+    VERBATIM
+  )
+
+  add_custom_target(lowfat_config_generated DEPENDS ${LOWFAT_GENERATED_HEADER})
+  set(LOWFAT_DEPS lowfat_config_generated)
+
+  # Make the generated header visible to all sources in this directory.
+  include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+  # Activate the custom-config code paths in the runtime AND the LLVM pass.
+  list(APPEND LOWFAT_COMMON_DEFINITIONS LOWFAT_CUSTOM_CONFIG=1)
+
+  message(STATUS "LowFat: custom config enabled: ${LOWFAT_SIZES_CFG}")
+  message(STATUS "LowFat: generated header:      ${LOWFAT_GENERATED_HEADER}")
+else()
+  message(STATUS "LowFat: using default POW2-only mode (no LOWFAT_SIZES_CFG set)")
+  set(LOWFAT_DEPS)
+endif()
 set(LOWFAT_SOURCES
   lf_rtl.cpp
   lf_interceptors.cpp
@@ -13,7 +69,6 @@ set(LOWFAT_HEADERS
 
 set(LOWFAT_CFLAGS ${SANITIZER_COMMON_CFLAGS})
 append_rtti_flag(OFF LOWFAT_CFLAGS)
-set(LOWFAT_COMMON_DEFINITIONS)
 
 # Static runtime library.
 add_compiler_rt_component(lowfat)
@@ -35,6 +90,7 @@ if(COMPILER_RT_HAS_LOWFAT)
       CFLAGS ${LOWFAT_CFLAGS}
       LINK_FLAGS ${WEAK_SYMBOL_LINK_FLAGS}
       DEFS ${LOWFAT_COMMON_DEFINITIONS}
+      DEPS ${LOWFAT_DEPS}
       PARENT_TARGET lowfat)
   else()
     add_compiler_rt_runtime(clang_rt.lowfat
@@ -49,6 +105,7 @@ if(COMPILER_RT_HAS_LOWFAT)
                   RTSanitizerCommonSymbolizerInternal
       CFLAGS ${LOWFAT_CFLAGS}
       DEFS ${LOWFAT_COMMON_DEFINITIONS}
+      DEPS ${LOWFAT_DEPS}
       PARENT_TARGET lowfat)
   endif()
 endif()
diff --git a/compiler-rt/lib/lowfat/lf_config.h b/compiler-rt/lib/lowfat/lf_config.h
index 02deb4375fff3..a3451ce2cd6a0 100644
--- a/compiler-rt/lib/lowfat/lf_config.h
+++ b/compiler-rt/lib/lowfat/lf_config.h
@@ -11,16 +11,24 @@
 // LowFat pointers encode allocation bounds directly in the pointer value:
 // - Memory is divided into regions, each for a specific size class
 // - Within each region, allocations are aligned to their size class
-// - Given a pointer, the base can be computed by masking off low bits
+// - Given a pointer, the base can be computed by masking off low bits (POW2)
+//   or via fixed-point magic-number math (non-POW2)
 // - The size can be looked up from a table using the region index
 //
-// Memory Layout (64-bit, example):
+// Default Memory Layout (POW2-only mode, kRegionSizeLog=32):
 //   Region 0: [0x10_0000_0000, 0x20_0000_0000) - 16-byte allocations
 //   Region 1: [0x20_0000_0000, 0x30_0000_0000) - 32-byte allocations
 //   Region 2: [0x30_0000_0000, 0x40_0000_0000) - 64-byte allocations
 //   ...
 //   Region N: [0xN0_0000_0000, ...)            - 2^(N+4)-byte allocations
 //
+// Custom Config Mode (LOWFAT_CUSTOM_CONFIG, kRegionSizeLog=35):
+//   Non-POW2 sizes (e.g. 48, 80, 96 bytes) are also supported.
+//   kRegionSizeLog increases to 35 (32 GB per region) to preserve precision
+//   of the magic-number arithmetic across the full region.
+//   The key helpers (SizeClassIndex, SizeClassToSize) switch to table lookups.
+//   All other logic (GetBase, GetSize, CheckBounds, etc.) is unchanged.
+//
 //===----------------------------------------------------------------------===//
 
 #ifndef LF_CONFIG_H
@@ -28,6 +36,10 @@
 
 #include "sanitizer_common/sanitizer_internal_defs.h"
 
+#ifdef LOWFAT_CUSTOM_CONFIG
+#include "lf_config_generated.h"
+#endif
+
 namespace __lowfat {
 
 using namespace __sanitizer;
@@ -40,6 +52,26 @@ using namespace __sanitizer;
 constexpr uptr kMinSizeLog = 4;  // 16 bytes
 constexpr uptr kMinSize = 1ULL << kMinSizeLog;
 
+#ifdef LOWFAT_CUSTOM_CONFIG
+
+constexpr uptr kNumSizeClasses = LOWFAT_NUM_SIZE_CLASSES;
+constexpr uptr kMaxSize = LOWFAT_MAX_SIZE;
+
+// SizeClassIndex: table lookup (binary search on kLowFatGenSizes[])
+// Replaces the POW2-only __builtin_clzll math.
+inline uptr SizeClassIndex(uptr size) {
+  return (uptr)lowfat_size_to_class((uint64_t)size);
+}
+
+// SizeClassToSize: direct table lookup — works for both POW2 and non-POW2.
+inline uptr SizeClassToSize(uptr class_index) {
+  if (class_index >= kNumSizeClasses)
+    return 0;
+  return (uptr)kLowFatGenSizes[class_index];
+}
+
+#else
+
 // Maximum allocation size (must be power of 2)
 constexpr uptr kMaxSizeLog = 30;  // 1 GB
 constexpr uptr kMaxSize = 1ULL << kMaxSizeLog;
@@ -65,12 +97,19 @@ inline uptr SizeClassToSize(uptr class_index) {
   return 1ULL << (class_index + kMinSizeLog);
 }
 
+#endif // LOWFAT_CUSTOM_CONFIG
+
 //===----------------------------------------------------------------------===//
 // Memory Region Configuration
 //===----------------------------------------------------------------------===//
 
+#ifdef LOWFAT_CUSTOM_CONFIG
+constexpr uptr kRegionSizeLog = LOWFAT_REGION_SIZE_LOG; // 35
+#else
 // Each region is 4GB (32 bits of address space per region)
 constexpr uptr kRegionSizeLog = 32;
+#endif
+
 constexpr uptr kRegionSize = 1ULL << kRegionSizeLog;
 
 // Base address where LowFat regions start
@@ -100,6 +139,50 @@ inline bool IsLowFatPointer(uptr ptr) {
 // Bounds Computation
 //===----------------------------------------------------------------------===//
 
+// Get the allocation size from a LowFat pointer
+inline uptr GetSize(uptr ptr) {
+  uptr region = GetRegionIndex(ptr);
+  if (region >= kNumSizeClasses)
+    return 0;  // Not a valid LowFat pointer
+  return SizeClassToSize(region);
+}
+
+#ifdef LOWFAT_CUSTOM_CONFIG
+
+// GetBase override for non-POW2: use magic-number multiplication instead
+// of the bitwise-AND fast path when the size class is not a power of two.
+//
+// For POW2 sizes:    base = ptr & ~(size - 1)           [fast path]
+// For non-POW2:      base = ((u128)ptr * magic >> 64) * size  [magic path]
+inline uptr GetBase(uptr ptr) {
+  uptr region = GetRegionIndex(ptr);
+  if (region >= kNumSizeClasses)
+    return 0;
+  if (kLowFatGenIsPow2[region]) {
+    // Fast path: bitwise AND
+    return ptr & (uptr)kLowFatGenMasks[region];
+  } else {
+    // Magic-number fixed-point path
+    typedef unsigned __int128 u128;
+    u128 mul  = (u128)ptr * (u128)kLowFatGenMagics[region];
+    uptr idx  = (uptr)(mul >> 64);
+    return idx * (uptr)kLowFatGenSizes[region];
+  }
+}
+
+// CheckBounds override: uses the custom GetBase above.
+inline bool CheckBounds(uptr ptr, uptr access_size) {
+  uptr region = GetRegionIndex(ptr);
+  if (region >= kNumSizeClasses)
+    return true;  // Not a LowFat pointer — assume valid
+  uptr alloc_size = SizeClassToSize(region);
+  uptr base       = GetBase(ptr);
+  uptr end        = base + alloc_size;
+  return (ptr + access_size) <= end;
+}
+
+#else
+
 // Get the base address of an allocation from a LowFat pointer
 // This uses the key LowFat insight: allocations are aligned to their size
 inline uptr GetBase(uptr ptr) {
@@ -112,14 +195,6 @@ inline uptr GetBase(uptr ptr) {
   return ptr & mask;
 }
 
-// Get the allocation size from a LowFat pointer
-inline uptr GetSize(uptr ptr) {
-  uptr region = GetRegionIndex(ptr);
-  if (region >= kNumSizeClasses)
-    return 0;  // Not a valid LowFat pointer
-  return SizeClassToSize(region);
-}
-
 // Check if ptr..ptr+access_size is within bounds
 inline bool CheckBounds(uptr ptr, uptr access_size) {
   uptr region = GetRegionIndex(ptr);
@@ -133,6 +208,8 @@ inline bool CheckBounds(uptr ptr, uptr access_size) {
   return (ptr + access_size) <= end;
 }
 
+#endif // LOWFAT_CUSTOM_CONFIG
+
 //===----------------------------------------------------------------------===//
 // Region Table (for lookup by region index)
 //===----------------------------------------------------------------------===//
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 94c7985569794..b7d0b37e1450f 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -33,27 +33,36 @@ bool lowfat_inited = false;
 // interceptor-level OOB (memset/memcpy/memmove) warns-and-continues or aborts.
 bool lowfat_recover = false;
 
+// Maximum number of size classes across both modes.
+// In POW2-only mode kNumSizeClasses=27; with a custom config it can be up to
+// LOWFAT_MAX_ARRAY_SIZE. We size the static arrays at compile time using
+// whichever is larger so the same translation unit works in both modes.
+#ifdef LOWFAT_CUSTOM_CONFIG
+  static constexpr uptr kMaxSizeClasses = LOWFAT_NUM_SIZE_CLASSES;
+#else
+  static constexpr uptr kMaxSizeClasses = kNumSizeClasses;
+#endif
+
 // Region table - initialized in __lf_init
-// TODO: not actually needed, to use for convenience
-RegionInfo kRegions[kNumSizeClasses];
+RegionInfo kRegions[kMaxSizeClasses];
 
 // Pointers to the start of each mapped region
-static uptr region_bases[kNumSizeClasses];
+static uptr region_bases[kMaxSizeClasses];
 
 // Bump pointer: next fresh address to allocate from in each region
-static uptr region_next_alloc[kNumSizeClasses];
+static uptr region_next_alloc[kMaxSizeClasses];
 
 // Segregated free lists: one singly-linked list per size class
 // Free blocks store a pointer to the next free block at their start
 struct FreeBlock {
   FreeBlock *next;
 };
-static FreeBlock *free_lists[kNumSizeClasses];
+static FreeBlock *free_lists[kMaxSizeClasses];
 
 // Per-size-class spin mutexes protecting region_next_alloc and free_lists.
 // Using one lock per size class allows concurrent allocation across different
 // size classes, which is the common case in multi-threaded programs.
-static StaticSpinMutex region_locks[kNumSizeClasses];
+static StaticSpinMutex region_locks[kMaxSizeClasses];
 
 static void InitializeFlags() {
   SetCommonFlagsDefaults();
@@ -78,9 +87,15 @@ static void InitializeFlags() {
 static void InitRegionTable() {
   for (uptr i = 0; i < kNumSizeClasses; i++) {
     uptr size = SizeClassToSize(i);
-    kRegions[i].size = size;
+    kRegions[i].size      = size;
     kRegions[i].alignment = size;
+#ifdef LOWFAT_CUSTOM_CONFIG
+    // For non-POW2 sizes the mask is meaningless (base computed via magic
+    // multiply); store 0 to make this explicit and catch accidental usage.
+    kRegions[i].mask = kLowFatGenIsPow2[i] ? (uptr)kLowFatGenMasks[i] : 0;
+#else
     kRegions[i].mask = ~(size - 1);
+#endif
     free_lists[i] = nullptr;
   }
 }
@@ -92,21 +107,25 @@ static bool InitMemoryRegions() {
     uptr region_start = GetRegionStart(i);
     
     // Reserve the region without committing physical memory
-    // MmapFixedNoReserve maps memory but doesn't allocate physical pages
-    // until they're accessed (lazy allocation)
+    // MmapFixedNoReserve maps memory but doesn't allocate physical pages until they're accessed
     bool success = MmapFixedNoReserve(region_start, kRegionSize, "lowfat_region");
     
-    if (!success) {
-      // Printf("LowFat: Failed to map region %zu at 0x%zx\n", i, region_start);
+    if (!success)
       return false;
-    }
     
     region_bases[i] = region_start;
-    region_next_alloc[i] = region_start;
+    
+    // The first allocation must be aligned to the object size relative to absolute zero.
+    // This is required for the magic-number fixed point math to securely compute object bases.
+    uptr size = kRegions[i].size;
+    uptr offset = region_start % size;
+    uptr initial_alloc = region_start;
+    if (offset != 0)
+      initial_alloc += (size - offset);
+      
+    region_next_alloc[i] = initial_alloc;
   }
   
-  // Printf("LowFat: Mapped %zu regions starting at 0x%zx\n", 
-  //        kNumSizeClasses, kRegionBase);
   return true;
 }
 
@@ -138,9 +157,6 @@ void *Allocate(uptr size) {
   uptr region_end = GetRegionStart(class_index) + kRegionSize;
   uptr addr = region_next_alloc[class_index];
 
-  // Ensure alignment (should already be aligned)
-  addr = (addr + alloc_size - 1) & ~(alloc_size - 1);
-
   if (addr + alloc_size > region_end)
     return nullptr;
 
diff --git a/compiler-rt/lib/lowfat/tools/lf_config_gen.c b/compiler-rt/lib/lowfat/tools/lf_config_gen.c
new file mode 100644
index 0000000000000..e9cd3d6d6502e
--- /dev/null
+++ b/compiler-rt/lib/lowfat/tools/lf_config_gen.c
@@ -0,0 +1,357 @@
+//===-- lf_config_gen.c - LowFat Size Class Config Generator --------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Standalone C tool (no LLVM dependencies) that reads a sizes.cfg file and
+// emits lf_config_generated.h containing:
+//
+//   - kLowFatGenSizes[]   : actual object sizes for each region index
+//   - kLowFatGenMagics[]  : precomputed 2^64/S values for non-POW2 sizes
+//   - kLowFatGenIsPow2[]  : true for power-of-two size classes
+//   - kLowFatGenMasks[]   : alignment masks for POW2 sizes (0 for non-POW2)
+//   - lowfat_size_to_class(): binary-search mapping from alloc size → region index
+//
+// sizes.cfg format:
+//   - One size per line (plain integer)
+//   - Sizes must be multiples of 16
+//   - First size must be 16
+//   - Sizes must be in strictly ascending order
+//   - Maximum size ≤ LOWFAT_REGION_SIZE_LOG (32 GB when kRegionSizeLog=35)
+//
+// Usage:
+//   lf_config_gen <sizes.cfg> <output_header>
+//
+// The precision checker validates that the fixed-point formula
+//   base = (ptr * magic >> 64) * S
+// correctly identifies the start of every object within a 32 GB region.
+// Where rounding errors exist, the effective size is reduced by the error
+// so the allocator never hands out the last few bytes that would be
+// mis-identified.
+//
+//===----------------------------------------------------------------------===//
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// --------------------------------------------------------------------------
+// Configuration constants (must stay in sync with lf_config.h)
+// --------------------------------------------------------------------------
+
+// Each region is 32 GB when custom config is active.
+// The original LowFat research proves the magic-number math is precise up to
+// 32 GB boundaries; 4 GB is insufficient for non-POW2 sizes.
+#define REGION_SIZE_LOG 35
+#define REGION_SIZE     ((uint64_t)1 << REGION_SIZE_LOG)   // 32 GB
+#define MAX_SIZE_CLASSES 256
+
+// Minimum alignment / granularity
+#define MIN_SIZE 16
+
+// --------------------------------------------------------------------------
+// __int128 helpers (standard C99/C11 with GCC/Clang extension)
+// --------------------------------------------------------------------------
+
+typedef unsigned __int128 u128;
+
+// Compute ceil(2^64 / S) using 128-bit arithmetic.
+// This is the magic number M such that floor(P / S) = (P * M) >> 64
+// for all P in [0, REGION_SIZE).
+static uint64_t compute_magic(uint64_t S) {
+  if (S == 0) return 0;
+  u128 two64 = (u128)1 << 64;
+  uint64_t q = (uint64_t)(two64 / S);
+  uint64_t r = (uint64_t)(two64 % S);
+  return q + (r != 0 ? 1 : 0);  // ceil division
+}
+
+// Returns 1 if n is an exact power of two, 0 otherwise.
+static int is_pow2(uint64_t n) {
+  return n != 0 && (n & (n - 1)) == 0;
+}
+
+// --------------------------------------------------------------------------
+// Precision Checker
+//
+// For each non-POW2 size S with magic M, scan backwards from the end of the
+// region to find the first pointer P where the reconstructed base is wrong.
+// Returns the number of bytes to subtract from S (the "error margin").
+//
+// The formula base(P) = ((u128)P * M >> 64) * S gives the start of the
+// object containing P.  For it to be correct we need:
+//   base(P) <= P  &&  P < base(P) + S
+//
+// We walk from REGION_SIZE - 1 down to 0 in steps of S, stopping at the
+// first P where the formula breaks.
+// --------------------------------------------------------------------------
+
+static uint64_t precision_error(uint64_t S, uint64_t M) {
+  if (is_pow2(S))
+    return 0;  // POW2 uses bitwise AND, no rounding error
+
+  uint64_t region = REGION_SIZE;
+  uint64_t error  = 0;
+
+  // Walk the last few objects (the vulnerable zone is at the top of the region)
+  // Limit scan to last 1024 objects to keep tool fast; real errors are tiny.
+  uint64_t scan_start = region > S * 1024 ? region - S * 1024 : 0;
+
+  for (uint64_t ptr = region - 1; ptr >= scan_start && ptr != (uint64_t)-1; ptr--) {
+    u128 mul  = (u128)ptr * (u128)M;
+    uint64_t idx  = (uint64_t)(mul >> 64);
+    uint64_t base = idx * S;
+
+    // base must be the correct start: base <= ptr < base + S
+    if (ptr < base || ptr >= base + S) {
+      // ptr is mis-identified; error = bytes from end of last good object to ptr
+      // We shrink S by the difference so the allocator stays safe.
+      uint64_t last_good_end = (ptr / S) * S;
+      error = ptr - last_good_end + 1;
+      break;
+    }
+  }
+  return error;
+}
+
+// --------------------------------------------------------------------------
+// Main
+// --------------------------------------------------------------------------
+
+int main(int argc, char *argv[]) {
+  if (argc != 3) {
+    fprintf(stderr, "Usage: %s <sizes.cfg> <output_header>\n", argv[0]);
+    return 1;
+  }
+
+  const char *cfg_path = argv[1];
+  const char *out_path = argv[2];
+
+  // ---- Parse sizes.cfg ----
+  FILE *cfg = fopen(cfg_path, "r");
+  if (!cfg) {
+    fprintf(stderr, "Error: cannot open '%s'\n", cfg_path);
+    return 1;
+  }
+
+  uint64_t sizes[MAX_SIZE_CLASSES];
+  int      num_sizes = 0;
+  char     line[256];
+
+  while (fgets(line, sizeof(line), cfg)) {
+    // Skip blank lines and comments
+    char *p = line;
+    while (*p == ' ' || *p == '\t') p++;
+    if (*p == '#' || *p == '\n' || *p == '\r' || *p == '\0')
+      continue;
+
+    uint64_t s = (uint64_t)strtoull(p, NULL, 10);
+    if (s == 0) continue;
+
+    if (num_sizes >= MAX_SIZE_CLASSES) {
+      fprintf(stderr, "Error: too many size classes (max %d)\n", MAX_SIZE_CLASSES);
+      fclose(cfg);
+      return 1;
+    }
+    sizes[num_sizes++] = s;
+  }
+  fclose(cfg);
+
+  if (num_sizes == 0) {
+    fprintf(stderr, "Error: no valid sizes found in '%s'\n", cfg_path);
+    return 1;
+  }
+
+  // ---- Validate ----
+  if (sizes[0] != MIN_SIZE) {
+    fprintf(stderr, "Error: first size must be %d, got %" PRIu64 "\n",
+            MIN_SIZE, sizes[0]);
+    return 1;
+  }
+  for (int i = 0; i < num_sizes; i++) {
+    if (sizes[i] % MIN_SIZE != 0) {
+      fprintf(stderr, "Error: size %" PRIu64 " is not a multiple of %d\n",
+              sizes[i], MIN_SIZE);
+      return 1;
+    }
+    if (sizes[i] > REGION_SIZE) {
+      fprintf(stderr, "Error: size %" PRIu64 " exceeds max region size %" PRIu64 "\n",
+              sizes[i], REGION_SIZE);
+      return 1;
+    }
+    if (i > 0 && sizes[i] <= sizes[i - 1]) {
+      fprintf(stderr, "Error: sizes must be strictly ascending; "
+              "sizes[%d]=%" PRIu64 " <= sizes[%d]=%" PRIu64 "\n",
+              i, sizes[i], i - 1, sizes[i - 1]);
+      return 1;
+    }
+  }
+
+  // ---- Compute tables ----
+  uint64_t magics[MAX_SIZE_CLASSES];
+  int      is_pow2_arr[MAX_SIZE_CLASSES];
+  uint64_t masks[MAX_SIZE_CLASSES];
+  uint64_t effective_sizes[MAX_SIZE_CLASSES];  // sizes adjusted for precision errors
+
+  for (int i = 0; i < num_sizes; i++) {
+    uint64_t S = sizes[i];
+    int pow2   = is_pow2(S);
+
+    is_pow2_arr[i] = pow2;
+
+    if (pow2) {
+      magics[i]         = 0;                // unused — POW2 uses AND
+      masks[i]          = ~(S - 1);
+      effective_sizes[i] = S;              // no precision error for POW2
+    } else {
+      uint64_t M    = compute_magic(S);
+      uint64_t err  = precision_error(S, M);
+      magics[i]     = M;
+      masks[i]      = 0;                   // not applicable for non-POW2
+      // Shrink effective size by error so allocator never gives out the
+      // bytes that the magic-number math would mis-identify.
+      effective_sizes[i] = S - err;
+
+      if (err > 0) {
+        fprintf(stderr, "Note: size %" PRIu64 " has precision error %" PRIu64
+                " bytes; effective size = %" PRIu64 "\n", S, err, effective_sizes[i]);
+      }
+    }
+  }
+
+  // ---- Open output ----
+  FILE *out = fopen(out_path, "w");
+  if (!out) {
+    fprintf(stderr, "Error: cannot open output '%s'\n", out_path);
+    return 1;
+  }
+
+  // ---- Emit header ----
+  fprintf(out,
+    "//===-- lf_config_generated.h - Auto-generated LowFat config ---------===//\n"
+    "//\n"
+    "// AUTO-GENERATED by lf_config_gen. DO NOT EDIT.\n"
+    "// Source: %s\n"
+    "//\n"
+    "//===----------------------------------------------------------------------===//\n"
+    "\n"
+    "#pragma once\n"
+    "#ifndef LF_CONFIG_GENERATED_H\n"
+    "#define LF_CONFIG_GENERATED_H\n"
+    "\n"
+    "#include <stdint.h>\n"
+    "\n"
+    "// Region layout: each region is 32 GB, matching the precision bounds of the\n"
+    "// magic-number fixed-point arithmetic proven in the original LowFat research.\n"
+    "#define LOWFAT_CUSTOM_CONFIG       1\n"
+    "#define LOWFAT_REGION_SIZE_LOG     35\n"
+    "#define LOWFAT_REGION_SIZE         (UINT64_C(1) << LOWFAT_REGION_SIZE_LOG)\n"
+    "#define LOWFAT_NUM_SIZE_CLASSES    %d\n"
+    "#define LOWFAT_MAX_SIZE            UINT64_C(%" PRIu64 ")\n"
+    "\n",
+    cfg_path,
+    num_sizes,
+    sizes[num_sizes - 1]
+  );
+
+  // kLowFatGenSizes
+  fprintf(out,
+    "// Actual allocation size for each region index.\n"
+    "// For non-POW2 sizes this is the precision-adjusted effective size.\n"
+    "static const uint64_t kLowFatGenSizes[LOWFAT_NUM_SIZE_CLASSES] = {\n"
+    "    /* idx: size */\n"
+  );
+  for (int i = 0; i < num_sizes; i++) {
+    fprintf(out, "    /* %3d */ UINT64_C(%" PRIu64 ")%s\n",
+            i, effective_sizes[i], (i < num_sizes - 1) ? "," : "");
+  }
+  fprintf(out, "};\n\n");
+
+  // kLowFatGenMagics
+  fprintf(out,
+    "// Magic numbers for non-POW2 sizes: M = ceil(2^64 / S).\n"
+    "// For POW2 sizes this is 0 (they use the AND fast path).\n"
+    "static const uint64_t kLowFatGenMagics[LOWFAT_NUM_SIZE_CLASSES] = {\n"
+    "    /* idx: magic */\n"
+  );
+  for (int i = 0; i < num_sizes; i++) {
+    fprintf(out, "    /* %3d */ UINT64_C(0x%016" PRIx64 ")%s  // size=%" PRIu64 "%s\n",
+            i, magics[i], (i < num_sizes - 1) ? "," : " ",
+            sizes[i], is_pow2_arr[i] ? " (POW2, unused)" : "");
+  }
+  fprintf(out, "};\n\n");
+
+  // kLowFatGenIsPow2
+  fprintf(out,
+    "// True if this size class is a power-of-two (uses AND path, not MUL path).\n"
+    "static const int kLowFatGenIsPow2[LOWFAT_NUM_SIZE_CLASSES] = {\n"
+    "    /* idx: isPow2 */\n"
+  );
+  for (int i = 0; i < num_sizes; i++) {
+    fprintf(out, "    /* %3d */ %d%s  // %" PRIu64 "\n",
+            i, is_pow2_arr[i], (i < num_sizes - 1) ? "," : " ", sizes[i]);
+  }
+  fprintf(out, "};\n\n");
+
+  // kLowFatGenMasks
+  fprintf(out,
+    "// Alignment masks for POW2 sizes: ~(S-1). Zero for non-POW2 sizes.\n"
+    "static const uint64_t kLowFatGenMasks[LOWFAT_NUM_SIZE_CLASSES] = {\n"
+    "    /* idx: mask */\n"
+  );
+  for (int i = 0; i < num_sizes; i++) {
+    fprintf(out, "    /* %3d */ UINT64_C(0x%016" PRIx64 ")%s  // size=%" PRIu64 "\n",
+            i, masks[i], (i < num_sizes - 1) ? "," : " ", sizes[i]);
+  }
+  fprintf(out, "};\n\n");
+
+  // lowfat_size_to_class: binary search — replaces SizeClassIndex()
+  fprintf(out,
+    "// Maps a requested allocation size to the smallest region index whose\n"
+    "// effective size >= requested size.  Replaces SizeClassIndex() when\n"
+    "// LOWFAT_CUSTOM_CONFIG is active.\n"
+    "// Returns LOWFAT_NUM_SIZE_CLASSES if size exceeds all size classes.\n"
+    "static inline uint64_t lowfat_size_to_class(uint64_t size) {\n"
+    "    // Binary search over kLowFatGenSizes[]\n"
+    "    if (size == 0) size = 1;\n"
+    "    uint64_t lo = 0, hi = LOWFAT_NUM_SIZE_CLASSES;\n"
+    "    while (lo < hi) {\n"
+    "        uint64_t mid = lo + (hi - lo) / 2;\n"
+    "        if (kLowFatGenSizes[mid] < size)\n"
+    "            lo = mid + 1;\n"
+    "        else\n"
+    "            hi = mid;\n"
+    "    }\n"
+    "    return lo;  // lo == LOWFAT_NUM_SIZE_CLASSES means no fit\n"
+    "}\n"
+    "\n"
+    "#endif  // LF_CONFIG_GENERATED_H\n"
+  );
+
+  fclose(out);
+
+  fprintf(stderr, "lf_config_gen: generated '%s' with %d size classes\n",
+          out_path, num_sizes);
+
+  // Print summary table to stdout
+  printf("Size Class Table:\n");
+  printf("  %-5s  %-10s  %-6s  %-20s  %-20s  %-10s\n",
+         "Idx", "ReqSize", "POW2?", "EffectiveSize", "Magic", "Mask");
+  printf("  %s\n", "---------------------------------------------------------------------");
+  for (int i = 0; i < num_sizes; i++) {
+    printf("  %-5d  %-10" PRIu64 "  %-6s  %-20" PRIu64 "  %#-20" PRIx64 "  %#-10" PRIx64 "\n",
+           i, sizes[i],
+           is_pow2_arr[i] ? "yes" : "no",
+           effective_sizes[i],
+           magics[i],
+           masks[i]);
+  }
+
+  return 0;
+}
diff --git a/compiler-rt/lib/lowfat/tools/sizes.cfg b/compiler-rt/lib/lowfat/tools/sizes.cfg
new file mode 100644
index 0000000000000..19036ab03aa68
--- /dev/null
+++ b/compiler-rt/lib/lowfat/tools/sizes.cfg
@@ -0,0 +1,57 @@
+# LowFat Size Class Configuration
+#
+# One size per line. Rules:
+#   - First entry must be 16
+#   - All sizes must be multiples of 16
+#   - Sizes must be strictly ascending
+#   - Maximum size <= 32 GB (LOWFAT_REGION_SIZE)
+#
+# POW2 sizes use the fast bitwise-AND path.
+# Non-POW2 sizes use the magic-number fixed-point multiplication path.
+#
+# This default config demonstrates a mix of POW2 and non-POW2 sizes.
+# Edit to your application's allocation profile for best results.
+#
+
+# Small sizes
+16
+32
+48
+64
+80
+96
+128
+192
+256
+320
+384
+512
+640
+768
+1024
+1280
+1536
+2048
+2560
+3072
+4096
+6144
+8192
+12288
+16384
+32768
+65536
+131072
+262144
+524288
+1048576
+2097152
+4194304
+8388608
+16777216
+33554432
+67108864
+134217728
+268435456
+536870912
+1073741824
diff --git a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
index 36fef5c50ddd0..b6c2d11511faf 100644
--- a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
+++ b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
@@ -45,3 +45,45 @@ add_llvm_component_library(LLVMInstrumentation
   TransformUtils
   ProfileData
   )
+
+if(LOWFAT_SIZES_CFG)
+  # LLVM pass needs the generated header too. Since runtimes build AFTER LLVM,
+  # we must generate a local copy of the header for the pass to use.
+  #
+  # We use CMAKE_HOST_C_COMPILER (not add_executable) so that the generator is
+  # always built for the *host* machine. add_executable would produce a
+  # target-architecture binary on cross-compile setups (e.g. building for
+  # AArch64 on an x86 host) which cannot be executed during the build.
+  set(LOWFAT_CONFIG_GEN_SRC
+      ${LLVM_MAIN_SRC_DIR}/../compiler-rt/lib/lowfat/tools/lf_config_gen.c)
+  set(LOWFAT_CONFIG_GEN_BIN
+      ${CMAKE_CURRENT_BINARY_DIR}/lf_config_gen_llvm${CMAKE_EXECUTABLE_SUFFIX})
+  set(LOWFAT_GENERATED_HEADER ${CMAKE_CURRENT_BINARY_DIR}/lf_config_generated.h)
+
+  add_custom_command(
+    OUTPUT  ${LOWFAT_CONFIG_GEN_BIN}
+    COMMAND ${CMAKE_C_COMPILER} -std=c11 -O2
+            ${LOWFAT_CONFIG_GEN_SRC}
+            -o ${LOWFAT_CONFIG_GEN_BIN}
+    DEPENDS ${LOWFAT_CONFIG_GEN_SRC}
+    COMMENT "Compiling lf_config_gen host tool (for LLVM pass)"
+    VERBATIM
+  )
+
+  add_custom_command(
+    OUTPUT  ${LOWFAT_GENERATED_HEADER}
+    COMMAND ${LOWFAT_CONFIG_GEN_BIN} ${LOWFAT_SIZES_CFG} ${LOWFAT_GENERATED_HEADER}
+    DEPENDS ${LOWFAT_CONFIG_GEN_BIN} ${LOWFAT_SIZES_CFG}
+    COMMENT "Generating LowFat size config for LLVM pass"
+    VERBATIM
+  )
+
+  add_custom_target(lowfat_pass_config DEPENDS ${LOWFAT_GENERATED_HEADER})
+  add_dependencies(LLVMInstrumentation lowfat_pass_config)
+
+  set_property(SOURCE LowFatSanitizer.cpp APPEND PROPERTY COMPILE_DEFINITIONS "LOWFAT_CUSTOM_CONFIG=1")
+  set_property(SOURCE LowFatSanitizer.cpp APPEND PROPERTY OBJECT_DEPENDS ${LOWFAT_GENERATED_HEADER})
+
+  # Tell the compiler where to find the generated header
+  target_include_directories(LLVMInstrumentation PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+endif()
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 66170ec3dd367..169af093fa18f 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -29,6 +29,12 @@
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
 #include "llvm/Transforms/Utils/ModuleUtils.h"
 
+// When the build generates a custom size-class config, pull in the tables so
+// the pass can emit the right IR (AND vs. 128-bit magic multiply).
+#ifdef LOWFAT_CUSTOM_CONFIG
+#include "lf_config_generated.h"
+#endif
+
 using namespace llvm;
 
 #define DEBUG_TYPE "lowfat"
@@ -37,6 +43,7 @@ STATISTIC(NumInstrumentedLoads, "Number of loads instrumented");
 STATISTIC(NumInstrumentedStores, "Number of stores instrumented");
 STATISTIC(NumInstrumentedAtomics, "Number of atomic operations instrumented");
 STATISTIC(NumInstrumentedMemIntrinsics, "Number of mem intrinsics instrumented");
+STATISTIC(NumInstrumentedGEPs, "Number of GEP pointer-arithmetic operations instrumented");
 
 namespace {
 
@@ -65,12 +72,45 @@ class LowFatSanitizer {
   bool instrumentMemoryAccess(Instruction *I, Value *Ptr, Type *AccessTy);
   bool instrumentMemoryRange(Instruction *I, Value *Ptr, Value *Size,
                              bool IsWrite);
-
-  // Constants (must match lf_config.h)
-  static constexpr uint64_t RegionBase = 0x100000000000ULL;
-  static constexpr uint64_t RegionSizeLog = 32;
+  bool instrumentGEP(GetElementPtrInst *GEP);
+
+  // Emit the OOB-check block given a pre-computed (Base, AllocSize, PtrInt).
+  void emitOobCheck(IRBuilder<> &IRB, Value *PtrInt, Value *Base,
+                    Value *AllocSize, uint64_t FixedAccessSize,
+                    Value *DynAccessSize, Instruction *InsertBefore,
+                    bool IsWrite);
+
+#ifdef LOWFAT_CUSTOM_CONFIG
+  // Build the IR to compute (AllocSize, Base) using runtime table lookups when
+  // the region index is only known at runtime.
+  std::pair<Value *, Value *> emitDynamicBaseMagic(IRBuilder<> &IRB,
+                                                    Value *PtrInt,
+                                                    Value *RegionIndex);
+
+  // Lazily get or create the global arrays that mirror the generated tables.
+  GlobalVariable *getSizesTable();
+  GlobalVariable *getMagicsTable();
+  GlobalVariable *getIsPow2Table();
+  GlobalVariable *getMasksTable();
+
+  GlobalVariable *SizesTableGV  = nullptr;
+  GlobalVariable *MagicsTableGV = nullptr;
+  GlobalVariable *IsPow2TableGV = nullptr;
+  GlobalVariable *MasksTableGV  = nullptr;
+#endif
+
+  // Constants (kept in sync with lf_config.h / lf_config_generated.h)
+#ifdef LOWFAT_CUSTOM_CONFIG
+  static constexpr uint64_t RegionBase     = 0x100000000000ULL;
+  static constexpr uint64_t RegionSizeLog  = LOWFAT_REGION_SIZE_LOG;   // 35
+  static constexpr uint64_t NumSizeClasses = LOWFAT_NUM_SIZE_CLASSES;
+  static constexpr uint64_t MinSizeLog     = 4;  // unused in custom mode
+#else
+  static constexpr uint64_t RegionBase     = 0x100000000000ULL;
+  static constexpr uint64_t RegionSizeLog  = 32;
   static constexpr uint64_t NumSizeClasses = 27; // kMaxSizeLog(30) - kMinSizeLog(4) + 1
-  static constexpr uint64_t MinSizeLog = 4;
+  static constexpr uint64_t MinSizeLog     = 4;
+#endif
 };
 
 FunctionCallee LowFatSanitizer::getReportOobFn() {
@@ -107,55 +147,218 @@ FunctionCallee LowFatSanitizer::getWarnOobFn() {
   return WarnOobFn;
 }
 
+#ifdef LOWFAT_CUSTOM_CONFIG
+// ---------------------------------------------------------------------------
+// Custom-config pass helpers: table-accessor lazy initializers
+// ---------------------------------------------------------------------------
+//
+// We mirror the four kLowFatGen* arrays from lf_config_generated.h as LLVM
+// GlobalVariable constants embedded inside the module.  This lets the
+// optimiser see them as constant loads and fold them through inlining.
+//
+// Arrays are initialised once (lazy, per-module) with the same values that
+// lf_config_gen baked into the header.
+
+static GlobalVariable *makeConstantArray(Module &M, StringRef Name,
+                                          ArrayRef<uint64_t> Data,
+                                          Type *ElemTy) {
+  SmallVector<Constant *, 64> Elems;
+  for (uint64_t V : Data)
+    Elems.push_back(ConstantInt::get(ElemTy, V));
+  auto *ArrayTy = ArrayType::get(ElemTy, Elems.size());
+  auto *Init    = ConstantArray::get(ArrayTy, Elems);
+  auto *GV = new GlobalVariable(M, ArrayTy, /*isConstant=*/true,
+                                GlobalValue::PrivateLinkage, Init, Name);
+  GV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
+  return GV;
+}
+
+GlobalVariable *LowFatSanitizer::getSizesTable() {
+  if (!SizesTableGV) {
+    SmallVector<uint64_t, 64> D(kLowFatGenSizes,
+                                 kLowFatGenSizes + LOWFAT_NUM_SIZE_CLASSES);
+    SizesTableGV = makeConstantArray(M, "__lf_gen_sizes", D,
+                                     Type::getInt64Ty(M.getContext()));
+  }
+  return SizesTableGV;
+}
+
+GlobalVariable *LowFatSanitizer::getMagicsTable() {
+  if (!MagicsTableGV) {
+    SmallVector<uint64_t, 64> D(kLowFatGenMagics,
+                                 kLowFatGenMagics + LOWFAT_NUM_SIZE_CLASSES);
+    MagicsTableGV = makeConstantArray(M, "__lf_gen_magics", D,
+                                      Type::getInt64Ty(M.getContext()));
+  }
+  return MagicsTableGV;
+}
+
+GlobalVariable *LowFatSanitizer::getIsPow2Table() {
+  if (!IsPow2TableGV) {
+    SmallVector<uint64_t, 64> D;
+    for (int i = 0; i < LOWFAT_NUM_SIZE_CLASSES; ++i)
+      D.push_back((uint64_t)kLowFatGenIsPow2[i]);
+    IsPow2TableGV = makeConstantArray(M, "__lf_gen_ispow2", D,
+                                       Type::getInt8Ty(M.getContext()));
+  }
+  return IsPow2TableGV;
+}
+
+GlobalVariable *LowFatSanitizer::getMasksTable() {
+  if (!MasksTableGV) {
+    SmallVector<uint64_t, 64> D(kLowFatGenMasks,
+                                 kLowFatGenMasks + LOWFAT_NUM_SIZE_CLASSES);
+    MasksTableGV = makeConstantArray(M, "__lf_gen_masks", D,
+                                     Type::getInt64Ty(M.getContext()));
+  }
+  return MasksTableGV;
+}
+
+// ---------------------------------------------------------------------------
+// emitDynamicBaseMagic
+//
+// Given a runtime RegionIndex, emit IR that loads the per-class size and
+// magic from the embedded tables and returns (AllocSize, Base) as IntptrTy.
+//
+// Generated IR (conceptually):
+//
+//   %alloc_size = load i64, ptr getelementptr(__lf_gen_sizes, 0, %region_idx)
+//   %magic      = load i64, ptr getelementptr(__lf_gen_magics, 0, %region_idx)
+//   %is_pow2    = load i8,  ptr getelementptr(__lf_gen_ispow2, 0, %region_idx)
+//   %mask       = load i64, ptr getelementptr(__lf_gen_masks,  0, %region_idx)
+//
+//   ; AND path (POW2 fast path)
+//   %base_and   = and i64 %ptr, %mask
+//
+//   ; MUL path (non-POW2 magic multiply)
+//   %ptr128     = zext i64 %ptr to i128
+//   %magic128   = zext i64 %magic to i128
+//   %mul128     = mul i128 %ptr128, %magic128
+//   %idx128     = lshr i128 %mul128, 64
+//   %idx        = trunc i128 %idx128 to i64
+//   %base_mul   = mul i64 %idx, %alloc_size
+//
+//   ; Select based on is_pow2 flag
+//   %is_pow2_i1 = trunc i8 %is_pow2 to i1
+//   %base       = select i1 %is_pow2_i1, i64 %base_and, i64 %base_mul
+// ---------------------------------------------------------------------------
+std::pair<Value *, Value *>
+LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
+                                       Value *RegionIndex) {
+  LLVMContext &Ctx = M.getContext();
+  Type *I64Ty  = Type::getInt64Ty(Ctx);
+  Type *I128Ty = Type::getInt128Ty(Ctx);
+  Type *I8Ty   = Type::getInt8Ty(Ctx);
+
+  // Helper: GEP + load from a GlobalVariable array at runtime index.
+  auto loadFromTable = [&](GlobalVariable *GV, Type *ElemTy,
+                            Value *Idx) -> Value * {
+    Value *Zero = ConstantInt::get(Type::getInt64Ty(Ctx), 0);
+    // Extend RegionIndex to i64 if it's a different width
+    Value *Idx64 = IRB.CreateZExtOrTrunc(Idx, I64Ty);
+    Value *GEP   = IRB.CreateInBoundsGEP(GV->getValueType(), GV,
+                                          {Zero, Idx64});
+    return IRB.CreateLoad(ElemTy, GEP);
+  };
+
+  Value *AllocSize64 = loadFromTable(getSizesTable(),  I64Ty, RegionIndex);
+  Value *Magic64     = loadFromTable(getMagicsTable(), I64Ty, RegionIndex);
+  Value *IsPow2_8    = loadFromTable(getIsPow2Table(), I8Ty,  RegionIndex);
+  Value *Mask64      = loadFromTable(getMasksTable(),  I64Ty, RegionIndex);
+
+  // Narrow to IntptrTy (which is i64 on 64-bit targets)
+  Value *AllocSize = IRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
+  Value *Mask      = IRB.CreateZExtOrTrunc(Mask64, IntptrTy);
+
+  // --- AND (POW2) base ---
+  Value *BaseAnd = IRB.CreateAnd(PtrInt, Mask);
+
+  // --- MUL (non-POW2) base ---
+  Value *Ptr128   = IRB.CreateZExt(PtrInt, I128Ty);
+  Value *Magic128 = IRB.CreateZExt(IRB.CreateZExtOrTrunc(Magic64, IntptrTy),
+                                   I128Ty);
+  Value *Mul128   = IRB.CreateMul(Ptr128, Magic128);
+  Value *Idx128   = IRB.CreateLShr(Mul128, ConstantInt::get(I128Ty, 64));
+  Value *Idx      = IRB.CreateTrunc(Idx128, IntptrTy);
+  Value *BaseMul  = IRB.CreateMul(Idx, AllocSize);
+
+  // Select between the two paths
+  Value *IsPow2_1 = IRB.CreateTrunc(IsPow2_8, Type::getInt1Ty(Ctx));
+  Value *Base     = IRB.CreateSelect(IsPow2_1, BaseAnd, BaseMul);
+
+  return {AllocSize, Base};
+}
+#endif  // LOWFAT_CUSTOM_CONFIG
+
 bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
                                               Type *AccessTy) {
   TypeSize AccessSize = DL.getTypeStoreSize(AccessTy);
   if (AccessSize.isScalable())
     return false;
+  uint64_t FixedAccessSize = AccessSize.getFixedValue();
 
   IRBuilder<> IRB(I);
   Value *PtrInt = IRB.CreatePtrToInt(Ptr, IntptrTy);
 
   // 1. Get region index: (Ptr - RegionBase) >> RegionSizeLog
   Value *RegionBaseVal = ConstantInt::get(IntptrTy, RegionBase);
-  Value *RegionOffset = IRB.CreateSub(PtrInt, RegionBaseVal);
-  Value *RegionIndex = IRB.CreateLShr(RegionOffset, RegionSizeLog);
+  Value *RegionOffset  = IRB.CreateSub(PtrInt, RegionBaseVal);
+  Value *RegionIndex   = IRB.CreateLShr(RegionOffset, RegionSizeLog);
 
-  // 2. Check if LowFat pointer: Region < NumSizeClasses
-  Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
-  Value *IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+  // 2. Check if LowFat pointer: RegionIndex < NumSizeClasses
+  Value *MaxRegion  = ConstantInt::get(IntptrTy, NumSizeClasses);
+  Value *IsLowFat   = IRB.CreateICmpULT(RegionIndex, MaxRegion);
 
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
   IRBuilder<> ThenIRB(ThenTerm);
 
-  // 3. Compute bounds inside the 'then' block
+  bool IsWrite = isa<StoreInst>(I) || isa<AtomicRMWInst>(I) ||
+                 isa<AtomicCmpXchgInst>(I);
+
+#ifdef LOWFAT_CUSTOM_CONFIG
+  // --- Custom config: dual-path (AND vs. magic multiply) ---
+  // Emit dynamic table-lookup path; the static constant-folded path would
+  // require knowing the region index at IR-construction time, which is only
+  // possible for stack/global accesses. Heap accesses always go through here.
+  auto [AllocSize, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
+
+  Value *AccessSizeVal = ConstantInt::get(IntptrTy, FixedAccessSize);
+  Value *AccessEnd     = ThenIRB.CreateAdd(PtrInt, AccessSizeVal);
+  Value *End           = ThenIRB.CreateAdd(Base, AllocSize);
+  Value *IsOOB         = ThenIRB.CreateICmpUGT(AccessEnd, End);
+
+  Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
+  IRBuilder<> OobIRB(OobTerm);
+  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
+  Type *I8Ty = Type::getInt8Ty(M.getContext());
+  Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
+  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
+#else
+  // --- POW2-only mode: existing shift-and-mask logic ---
   Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
-  Value *ShiftAmount = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
-  Value *SizeOne = ConstantInt::get(IntptrTy, 1);
-  Value *AllocSize = ThenIRB.CreateShl(SizeOne, ShiftAmount);
-  Value *SizeMinusOne = ThenIRB.CreateSub(AllocSize, SizeOne);
-  Value *Mask = ThenIRB.CreateNot(SizeMinusOne);
-  Value *Base = ThenIRB.CreateAnd(PtrInt, Mask);
-  Value *End = ThenIRB.CreateAdd(Base, AllocSize);
-
-  // 4. Check access: Ptr + AccessSize <= End
-  Value *AccessSizeVal = ConstantInt::get(IntptrTy, AccessSize.getFixedValue());
-  Value *AccessEnd = ThenIRB.CreateAdd(PtrInt, AccessSizeVal);
-  Value *IsOOB = ThenIRB.CreateICmpUGT(AccessEnd, End);
+  Value *ShiftAmount   = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
+  Value *SizeOne       = ConstantInt::get(IntptrTy, 1);
+  Value *AllocSize     = ThenIRB.CreateShl(SizeOne, ShiftAmount);
+  Value *SizeMinusOne  = ThenIRB.CreateSub(AllocSize, SizeOne);
+  Value *Mask          = ThenIRB.CreateNot(SizeMinusOne);
+  Value *Base          = ThenIRB.CreateAnd(PtrInt, Mask);
+  Value *End           = ThenIRB.CreateAdd(Base, AllocSize);
+
+  Value *AccessSizeVal = ConstantInt::get(IntptrTy, FixedAccessSize);
+  Value *AccessEnd     = ThenIRB.CreateAdd(PtrInt, AccessSizeVal);
+  Value *IsOOB         = ThenIRB.CreateICmpUGT(AccessEnd, End);
 
   Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
   IRBuilder<> OobIRB(OobTerm);
-  
   FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
   Type *I8Ty = Type::getInt8Ty(M.getContext());
-  bool IsWrite = isa<StoreInst>(I) || isa<AtomicRMWInst>(I) ||
-                 isa<AtomicCmpXchgInst>(I);
   Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
   OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
+#endif
 
-  if (isa<LoadInst>(I)) NumInstrumentedLoads++;
-  else if (isa<StoreInst>(I)) NumInstrumentedStores++;
-  else NumInstrumentedAtomics++;
+  if (isa<LoadInst>(I))          NumInstrumentedLoads++;
+  else if (isa<StoreInst>(I))    NumInstrumentedStores++;
+  else                           NumInstrumentedAtomics++;
 
   return true;
 }
@@ -163,34 +366,37 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
 bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
                                              Value *Size, bool IsWrite) {
   IRBuilder<> IRB(I);
-  Value *PtrInt = IRB.CreatePtrToInt(Ptr, IntptrTy);
+  Value *PtrInt  = IRB.CreatePtrToInt(Ptr, IntptrTy);
   Value *SizeInt = IRB.CreateZExtOrTrunc(Size, IntptrTy);
 
   Value *RegionBaseVal = ConstantInt::get(IntptrTy, RegionBase);
-  Value *RegionOffset = IRB.CreateSub(PtrInt, RegionBaseVal);
-  Value *RegionIndex = IRB.CreateLShr(RegionOffset, RegionSizeLog);
+  Value *RegionOffset  = IRB.CreateSub(PtrInt, RegionBaseVal);
+  Value *RegionIndex   = IRB.CreateLShr(RegionOffset, RegionSizeLog);
 
   Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
-  Value *IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+  Value *IsLowFat  = IRB.CreateICmpULT(RegionIndex, MaxRegion);
 
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
   IRBuilder<> ThenIRB(ThenTerm);
 
+#ifdef LOWFAT_CUSTOM_CONFIG
+  auto [AllocSize, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
+#else
   Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
-  Value *ShiftAmount = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
-  Value *SizeOne = ConstantInt::get(IntptrTy, 1);
-  Value *AllocSize = ThenIRB.CreateShl(SizeOne, ShiftAmount);
-  Value *SizeMinusOne = ThenIRB.CreateSub(AllocSize, SizeOne);
-  Value *Mask = ThenIRB.CreateNot(SizeMinusOne);
-  Value *Base = ThenIRB.CreateAnd(PtrInt, Mask);
-  Value *End = ThenIRB.CreateAdd(Base, AllocSize);
-
+  Value *ShiftAmount   = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
+  Value *SizeOne       = ConstantInt::get(IntptrTy, 1);
+  Value *AllocSize     = ThenIRB.CreateShl(SizeOne, ShiftAmount);
+  Value *SizeMinusOne  = ThenIRB.CreateSub(AllocSize, SizeOne);
+  Value *Mask          = ThenIRB.CreateNot(SizeMinusOne);
+  Value *Base          = ThenIRB.CreateAnd(PtrInt, Mask);
+#endif
+
+  Value *End       = ThenIRB.CreateAdd(Base, AllocSize);
   Value *AccessEnd = ThenIRB.CreateAdd(PtrInt, SizeInt);
-  Value *IsOOB = ThenIRB.CreateICmpUGT(AccessEnd, End);
+  Value *IsOOB     = ThenIRB.CreateICmpUGT(AccessEnd, End);
 
   Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
   IRBuilder<> OobIRB(OobTerm);
-  
   FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
   Type *I8Ty = Type::getInt8Ty(M.getContext());
   Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
@@ -200,6 +406,81 @@ bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
   return true;
 }
 
+// ---------------------------------------------------------------------------
+// instrumentGEP
+//
+// Instruments a GetElementPtr instruction to catch OOB pointer arithmetic
+// before the out-of-bounds pointer can escape to a neighbouring allocation
+// slot (where a load/store check would misidentify it as valid).
+//
+// Key insight: use the SOURCE pointer's allocation bounds, not the result's.
+//
+//   ptr = p + 48  (src=p, result=p+48, alloc=48)
+//
+//   Load/store check on p+48:
+//     GetBase(p+48) = p+48  ← attributed to next slot
+//     End = p+96  →  p+49 ≤ p+96  →  NOT OOB (false negative)
+//
+//   GEP check using src=p:
+//     GetBase(p) = p, End = p+48
+//     result p+48 ≥ End  →  OOB (detected!)
+// ---------------------------------------------------------------------------
+bool LowFatSanitizer::instrumentGEP(GetElementPtrInst *GEP) {
+  // We need an insertion point after the GEP so we can use its result value.
+  Instruction *InsertPt = GEP->getNextNode();
+  if (!InsertPt)
+    return false;
+
+  IRBuilder<> IRB(InsertPt);
+
+  // SOURCE pointer — determines the allocation the GEP started from.
+  Value *SrcPtr = GEP->getPointerOperand();
+  Value *SrcInt = IRB.CreatePtrToInt(SrcPtr, IntptrTy);
+
+  // RESULT pointer — what we're checking stays within [Base, Base+AllocSize).
+  Value *ResInt = IRB.CreatePtrToInt(GEP, IntptrTy);
+
+  // 1. Is the source a LowFat pointer?
+  Value *RegionBaseVal = ConstantInt::get(IntptrTy, RegionBase);
+  Value *RegionOffset  = IRB.CreateSub(SrcInt, RegionBaseVal);
+  Value *RegionIndex   = IRB.CreateLShr(RegionOffset, RegionSizeLog);
+  Value *MaxRegion     = ConstantInt::get(IntptrTy, NumSizeClasses);
+  Value *IsLowFat      = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+
+  Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, InsertPt, false);
+  IRBuilder<> ThenIRB(ThenTerm);
+
+  // 2. Compute Base and AllocSize from the SOURCE pointer.
+#ifdef LOWFAT_CUSTOM_CONFIG
+  auto [AllocSize, Base] = emitDynamicBaseMagic(ThenIRB, SrcInt, RegionIndex);
+#else
+  Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
+  Value *ShiftAmount   = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
+  Value *SizeOne       = ConstantInt::get(IntptrTy, 1);
+  Value *AllocSize     = ThenIRB.CreateShl(SizeOne, ShiftAmount);
+  Value *SizeMinusOne  = ThenIRB.CreateSub(AllocSize, SizeOne);
+  Value *Mask          = ThenIRB.CreateNot(SizeMinusOne);
+  Value *Base          = ThenIRB.CreateAnd(SrcInt, Mask);
+#endif
+
+  // 3. OOB if result underflows (< Base) or reaches/passes the end (>= Base+AllocSize).
+  Value *End     = ThenIRB.CreateAdd(Base, AllocSize);
+  Value *TooLow  = ThenIRB.CreateICmpULT(ResInt, Base);
+  Value *TooHigh = ThenIRB.CreateICmpUGE(ResInt, End);
+  Value *IsOOB   = ThenIRB.CreateOr(TooLow, TooHigh);
+
+  Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
+  IRBuilder<> OobIRB(OobTerm);
+  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
+  Type *I8Ty = Type::getInt8Ty(M.getContext());
+  // GEPs are neither reads nor writes; report as read (0).
+  OobIRB.CreateCall(OobFn,
+                    {ResInt, Base, AllocSize, ConstantInt::get(I8Ty, 0)});
+
+  NumInstrumentedGEPs++;
+  return true;
+}
+
 bool LowFatSanitizer::instrumentFunction(Function &F) {
   bool Modified = false;
   SmallVector<Instruction *, 16> ToInstrument;
@@ -211,6 +492,8 @@ bool LowFatSanitizer::instrumentFunction(Function &F) {
         ToInstrument.push_back(&I);
       else if (isa<MemIntrinsic>(&I))
         ToInstrument.push_back(&I);
+      else if (isa<GetElementPtrInst>(&I))
+        ToInstrument.push_back(&I);
     }
   }
 
@@ -228,7 +511,8 @@ bool LowFatSanitizer::instrumentFunction(Function &F) {
     else if (auto *MT = dyn_cast<MemTransferInst>(I)) {
       Modified |= instrumentMemoryRange(I, MT->getDest(), MT->getLength(), true);
       Modified |= instrumentMemoryRange(I, MT->getSource(), MT->getLength(), false);
-    }
+    } else if (auto *GEP = dyn_cast<GetElementPtrInst>(I))
+      Modified |= instrumentGEP(GEP);
   }
   return Modified;
 }
diff --git a/llvm/runtimes/CMakeLists.txt b/llvm/runtimes/CMakeLists.txt
index f0ef353a2c66c..140a1c6aa38a5 100644
--- a/llvm/runtimes/CMakeLists.txt
+++ b/llvm/runtimes/CMakeLists.txt
@@ -547,6 +547,12 @@ if(build_runtimes)
   if(CMAKE_PROGRAM_PATH)
     list(APPEND extra_cmake_args "-DCMAKE_PROGRAM_PATH=${CMAKE_PROGRAM_PATH}")
   endif()
+  # Pass custom / reset LowFat Configuration file path to compiler-rt.
+  if(LOWFAT_SIZES_CFG)
+    list(APPEND extra_cmake_args "-DLOWFAT_SIZES_CFG=${LOWFAT_SIZES_CFG}")
+  else()
+    list(APPEND extra_cmake_args "-ULOWFAT_SIZES_CFG")
+  endif()
 
   # TODO: We need to consider passing it as '-DRUNTIMES_x86_64_LLVM_ENABLE_RUNTIMES'.
   if("libclc" IN_LIST LLVM_ENABLE_RUNTIMES)

>From 639359c4fcfc025c966d6941d62aa562ad0de4b5 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 7 Mar 2026 21:42:07 +0800
Subject: [PATCH 36/55] [LowFat] Add tests for custom mem sizes

---
 compiler-rt/test/lowfat/CMakeLists.txt        |  7 +++
 compiler-rt/test/lowfat/lit.cfg.py            |  6 +++
 compiler-rt/test/lowfat/lit.site.cfg.py.in    |  2 +
 compiler-rt/test/lowfat/non_pow2_inbounds.cpp | 20 ++++++++
 compiler-rt/test/lowfat/non_pow2_oob.cpp      | 25 ++++++++++
 .../test/lowfat/non_pow2_oob_boundary.cpp     | 49 +++++++++++++++++++
 6 files changed, 109 insertions(+)
 create mode 100644 compiler-rt/test/lowfat/non_pow2_inbounds.cpp
 create mode 100644 compiler-rt/test/lowfat/non_pow2_oob.cpp
 create mode 100644 compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp

diff --git a/compiler-rt/test/lowfat/CMakeLists.txt b/compiler-rt/test/lowfat/CMakeLists.txt
index 089d35f63d163..f1670497c1dca 100644
--- a/compiler-rt/test/lowfat/CMakeLists.txt
+++ b/compiler-rt/test/lowfat/CMakeLists.txt
@@ -19,6 +19,13 @@ foreach(arch ${LOWFAT_TEST_ARCH})
   string(TOUPPER ${arch} ARCH_UPPER_CASE)
   set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}Config)
 
+  # Propagate custom-config flag to lit as a Python boolean literal.
+  if(LOWFAT_SIZES_CFG)
+    set(LOWFAT_CUSTOM_CONFIG_ENABLED "True")
+  else()
+    set(LOWFAT_CUSTOM_CONFIG_ENABLED "False")
+  endif()
+
   configure_lit_site_cfg(
     ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
     ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py
diff --git a/compiler-rt/test/lowfat/lit.cfg.py b/compiler-rt/test/lowfat/lit.cfg.py
index 2c83322d99b94..ba7dd85d4bd13 100644
--- a/compiler-rt/test/lowfat/lit.cfg.py
+++ b/compiler-rt/test/lowfat/lit.cfg.py
@@ -70,3 +70,9 @@ def build_invocation(flags):
     # platform truly isn't supported.
     if hasattr(config, "target_os"):
         config.unsupported = True
+
+# Expose 'lowfat-custom-config' feature when the runtime was built with a
+# custom sizes.cfg (i.e. -DLOWFAT_SIZES_CFG was set at cmake time).
+# Tests guarded with REQUIRES: lowfat-custom-config are skipped otherwise.
+if getattr(config, "lowfat_custom_config", False):
+    config.available_features.add("lowfat-custom-config")
diff --git a/compiler-rt/test/lowfat/lit.site.cfg.py.in b/compiler-rt/test/lowfat/lit.site.cfg.py.in
index 1dbb2cb1367b6..c89b8d682c688 100644
--- a/compiler-rt/test/lowfat/lit.site.cfg.py.in
+++ b/compiler-rt/test/lowfat/lit.site.cfg.py.in
@@ -5,6 +5,8 @@ config.name_suffix = "@LOWFAT_TEST_CONFIG_SUFFIX@"
 config.target_cflags = "@LOWFAT_TEST_TARGET_CFLAGS@"
 config.clang = "@LOWFAT_TEST_TARGET_CC@"
 config.target_arch = "@LOWFAT_TEST_TARGET_ARCH@"
+# True when -DLOWFAT_SIZES_CFG was set at cmake time.
+config.lowfat_custom_config = @LOWFAT_CUSTOM_CONFIG_ENABLED@
 
 # Load common config for all compiler-rt lit tests.
 lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")
diff --git a/compiler-rt/test/lowfat/non_pow2_inbounds.cpp b/compiler-rt/test/lowfat/non_pow2_inbounds.cpp
new file mode 100644
index 0000000000000..633aeeb9f1e38
--- /dev/null
+++ b/compiler-rt/test/lowfat/non_pow2_inbounds.cpp
@@ -0,0 +1,20 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && %run %t
+
+// Verify that an in-bounds write to the last byte of a 48-byte allocation
+// does not trigger a false positive OOB error.
+//
+// REQUIRES: lowfat-custom-config
+
+#include <cstdlib>
+
+int main() {
+  char *p = (char *)malloc(48);
+  if (!p) return 1;
+
+  // Write to the very last byte of the 48-byte allocation.
+  // This must NOT trigger a LowFat error.
+  p[47] = 'x';
+
+  free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/non_pow2_oob.cpp b/compiler-rt/test/lowfat/non_pow2_oob.cpp
new file mode 100644
index 0000000000000..a79245447d67e
--- /dev/null
+++ b/compiler-rt/test/lowfat/non_pow2_oob.cpp
@@ -0,0 +1,25 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+// Verify that an OOB write 2 bytes past a 48-byte allocation is detected.
+// This test exercises the non-POW2 magic-multiply path: without custom config
+// the allocation would be silently rounded to 64 bytes, making p[50] valid.
+//
+// REQUIRES: lowfat-custom-config
+
+#include <cstdlib>
+
+int main() {
+  char *p = (char *)malloc(48);
+  if (!p) return 1;
+
+  // Write 8 bytes starting at offset 44.
+  // Bytes 44-51 will cross the 48-byte allocation boundary, straddling
+  // into the next object grid. This triggers the OOB error.
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
+  double *val = (double *)(p + 44);
+  *val = 1.0;
+
+  free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp b/compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp
new file mode 100644
index 0000000000000..23fd8bee42568
--- /dev/null
+++ b/compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp
@@ -0,0 +1,49 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+// Demonstrates GEP-level (pointer arithmetic) instrumentation catching an OOB
+// pointer that escapes to a neighbouring allocation slot — a case where
+// load/store checking alone produces a false negative.
+//
+// SCENARIO
+// --------
+// p is a 48-byte LowFat allocation at address k*48 (absolute-zero aligned).
+// p+48 = (k+1)*48 is the exact start of the NEXT 48-byte slot.
+//
+//   Load/store check on *(p+48):
+//     GetBase(p+48) = (k+1)*48 = p+48  <-- attributed to NEXT slot
+//     End           = p+48 + 48 = p+96
+//     AccessEnd     = p+48 + 1  = p+49
+//     p+49 <= p+96  --> NOT OOB  (false negative — sink() would not catch it)
+//
+//   GEP check at the arithmetic (p + 48) itself:
+//     GetBase(p)    = k*48 = p           <-- uses SOURCE pointer's bounds
+//     End           = p + 48
+//     result p+48   >= End               --> OOB (detected before escape!)
+//
+// By checking at the point of pointer arithmetic, the error is caught before
+// the OOB pointer reaches sink(), regardless of which slot it happens to land in.
+//
+// REQUIRES: lowfat-custom-config
+
+#include <stdlib.h>
+
+// Prevent inlining so the OOB pointer is forced to cross a function boundary,
+// making the "escape" explicit. The store inside sink() is NOT detectable by
+// load/store checking alone (GetBase(q) = q, so it appears in-bounds).
+__attribute__((noinline))
+static void sink(volatile char *q) { *q = 'x'; }
+
+int main() {
+  char *p = (char *)malloc(48);
+  if (!p) return 1;
+
+  // GEP check: p+48 is computed using p's bounds (End = p+48).
+  // result == End  -->  OOB detected here, before the pointer reaches sink().
+  // CHECK: LOWFAT ERROR: out-of-bounds error detected!
+  sink(p + 48);
+
+  free(p);
+  return 0;
+}
+

>From 528eadbca916af9d45ca67375642096b71050507 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 7 Mar 2026 21:48:25 +0800
Subject: [PATCH 37/55] [LowFat] Refactor LowFat custom cmake propagation into
 sep file

---
 .../Transforms/Instrumentation/CMakeLists.txt | 42 +------------------
 .../Instrumentation/LowFatPassConfig.cmake    | 41 ++++++++++++++++++
 2 files changed, 42 insertions(+), 41 deletions(-)
 create mode 100644 llvm/lib/Transforms/Instrumentation/LowFatPassConfig.cmake

diff --git a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
index b6c2d11511faf..023d00d410c92 100644
--- a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
+++ b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
@@ -46,44 +46,4 @@ add_llvm_component_library(LLVMInstrumentation
   ProfileData
   )
 
-if(LOWFAT_SIZES_CFG)
-  # LLVM pass needs the generated header too. Since runtimes build AFTER LLVM,
-  # we must generate a local copy of the header for the pass to use.
-  #
-  # We use CMAKE_HOST_C_COMPILER (not add_executable) so that the generator is
-  # always built for the *host* machine. add_executable would produce a
-  # target-architecture binary on cross-compile setups (e.g. building for
-  # AArch64 on an x86 host) which cannot be executed during the build.
-  set(LOWFAT_CONFIG_GEN_SRC
-      ${LLVM_MAIN_SRC_DIR}/../compiler-rt/lib/lowfat/tools/lf_config_gen.c)
-  set(LOWFAT_CONFIG_GEN_BIN
-      ${CMAKE_CURRENT_BINARY_DIR}/lf_config_gen_llvm${CMAKE_EXECUTABLE_SUFFIX})
-  set(LOWFAT_GENERATED_HEADER ${CMAKE_CURRENT_BINARY_DIR}/lf_config_generated.h)
-
-  add_custom_command(
-    OUTPUT  ${LOWFAT_CONFIG_GEN_BIN}
-    COMMAND ${CMAKE_C_COMPILER} -std=c11 -O2
-            ${LOWFAT_CONFIG_GEN_SRC}
-            -o ${LOWFAT_CONFIG_GEN_BIN}
-    DEPENDS ${LOWFAT_CONFIG_GEN_SRC}
-    COMMENT "Compiling lf_config_gen host tool (for LLVM pass)"
-    VERBATIM
-  )
-
-  add_custom_command(
-    OUTPUT  ${LOWFAT_GENERATED_HEADER}
-    COMMAND ${LOWFAT_CONFIG_GEN_BIN} ${LOWFAT_SIZES_CFG} ${LOWFAT_GENERATED_HEADER}
-    DEPENDS ${LOWFAT_CONFIG_GEN_BIN} ${LOWFAT_SIZES_CFG}
-    COMMENT "Generating LowFat size config for LLVM pass"
-    VERBATIM
-  )
-
-  add_custom_target(lowfat_pass_config DEPENDS ${LOWFAT_GENERATED_HEADER})
-  add_dependencies(LLVMInstrumentation lowfat_pass_config)
-
-  set_property(SOURCE LowFatSanitizer.cpp APPEND PROPERTY COMPILE_DEFINITIONS "LOWFAT_CUSTOM_CONFIG=1")
-  set_property(SOURCE LowFatSanitizer.cpp APPEND PROPERTY OBJECT_DEPENDS ${LOWFAT_GENERATED_HEADER})
-
-  # Tell the compiler where to find the generated header
-  target_include_directories(LLVMInstrumentation PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
-endif()
+include(${CMAKE_CURRENT_LIST_DIR}/LowFatPassConfig.cmake)
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatPassConfig.cmake b/llvm/lib/Transforms/Instrumentation/LowFatPassConfig.cmake
new file mode 100644
index 0000000000000..037415da4a488
--- /dev/null
+++ b/llvm/lib/Transforms/Instrumentation/LowFatPassConfig.cmake
@@ -0,0 +1,41 @@
+if(LOWFAT_SIZES_CFG)
+  # LLVM pass needs the generated header too. Since runtimes build AFTER LLVM,
+  # we must generate a local copy of the header for the pass to use.
+  #
+  # We use CMAKE_C_COMPILER (not add_executable) so that the generator is
+  # always built for the host machine. add_executable would produce a
+  # target-architecture binary on cross-compile setups (e.g. building for
+  # AArch64 on an x86 host) which cannot be executed during the build.
+  set(LOWFAT_CONFIG_GEN_SRC
+      ${LLVM_MAIN_SRC_DIR}/../compiler-rt/lib/lowfat/tools/lf_config_gen.c)
+  set(LOWFAT_CONFIG_GEN_BIN
+      ${CMAKE_CURRENT_BINARY_DIR}/lf_config_gen_llvm${CMAKE_EXECUTABLE_SUFFIX})
+  set(LOWFAT_GENERATED_HEADER ${CMAKE_CURRENT_BINARY_DIR}/lf_config_generated.h)
+
+  add_custom_command(
+    OUTPUT  ${LOWFAT_CONFIG_GEN_BIN}
+    COMMAND ${CMAKE_C_COMPILER} -std=c11 -O2
+            ${LOWFAT_CONFIG_GEN_SRC}
+            -o ${LOWFAT_CONFIG_GEN_BIN}
+    DEPENDS ${LOWFAT_CONFIG_GEN_SRC}
+    COMMENT "Compiling lf_config_gen host tool (for LLVM pass)"
+    VERBATIM
+  )
+
+  add_custom_command(
+    OUTPUT  ${LOWFAT_GENERATED_HEADER}
+    COMMAND ${LOWFAT_CONFIG_GEN_BIN} ${LOWFAT_SIZES_CFG} ${LOWFAT_GENERATED_HEADER}
+    DEPENDS ${LOWFAT_CONFIG_GEN_BIN} ${LOWFAT_SIZES_CFG}
+    COMMENT "Generating LowFat size config for LLVM pass"
+    VERBATIM
+  )
+
+  add_custom_target(lowfat_pass_config DEPENDS ${LOWFAT_GENERATED_HEADER})
+  add_dependencies(LLVMInstrumentation lowfat_pass_config)
+
+  set_property(SOURCE LowFatSanitizer.cpp APPEND PROPERTY COMPILE_DEFINITIONS "LOWFAT_CUSTOM_CONFIG=1")
+  set_property(SOURCE LowFatSanitizer.cpp APPEND PROPERTY OBJECT_DEPENDS ${LOWFAT_GENERATED_HEADER})
+
+  # Tell the compiler where to find the generated header.
+  target_include_directories(LLVMInstrumentation PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+endif()

>From 808047bc612cfc3d5daf19d56d6b07a45963845c Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 7 Mar 2026 21:56:00 +0800
Subject: [PATCH 38/55] [LowFat] Set fast mode as default in tests

---
 compiler-rt/test/lowfat/lit.cfg.py | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/compiler-rt/test/lowfat/lit.cfg.py b/compiler-rt/test/lowfat/lit.cfg.py
index ba7dd85d4bd13..948a4f0620d29 100644
--- a/compiler-rt/test/lowfat/lit.cfg.py
+++ b/compiler-rt/test/lowfat/lit.cfg.py
@@ -41,19 +41,17 @@ def build_invocation(flags):
 # Base flags
 lowfat_base = ["-fsanitize=lowfat"]
 
-# Fast mode (OptimizerLastEP)
-lowfat_fast = lowfat_base + ["-mllvm", "-lowfat-mode=fast"]
-# Safe mode (Barrier at PipelineStartEP + instrument at OptimizerLastEP)
+# safe mode (fast mode is the default)
 lowfat_safe = lowfat_base + ["-mllvm", "-lowfat-mode=safe"]
 
-config.substitutions.append(("%clangxx_lowfat ", build_invocation(lowfat_fast)))
+config.substitutions.append(("%clangxx_lowfat ", build_invocation(lowfat_base)))
 config.substitutions.append(("%clangxx_lowfat_safe ", build_invocation(lowfat_safe)))
 
 # Recover mode versions
 config.substitutions.append(
     (
         "%clangxx_lowfat_recover ",
-        build_invocation(lowfat_fast + ["-fsanitize-recover=lowfat"]),
+        build_invocation(lowfat_base + ["-fsanitize-recover=lowfat"]),
     )
 )
 config.substitutions.append(

>From bf1dadf6f6860ce6d0e2a661aafcee259436b5e1 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 7 Mar 2026 21:58:43 +0800
Subject: [PATCH 39/55] [LowFat] Update comments in tests

---
 compiler-rt/test/lowfat/dead_load.cpp            |  3 ++-
 .../test/lowfat/mode_baseline_all_detect.cpp     |  7 ++++---
 compiler-rt/test/lowfat/mode_diff_pure_call.cpp  | 16 ++++++++--------
 3 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/compiler-rt/test/lowfat/dead_load.cpp b/compiler-rt/test/lowfat/dead_load.cpp
index 15e30304506cd..8d4bd70c8c057 100644
--- a/compiler-rt/test/lowfat/dead_load.cpp
+++ b/compiler-rt/test/lowfat/dead_load.cpp
@@ -1,7 +1,8 @@
 // RUN: %clangxx_lowfat -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL
 // RUN: %clangxx_lowfat_safe -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL
 
-// Verifies that a genuine OOB heap read is detected in both Fast and Safe mode
+// Verifies that a genuine OOB heap read is detected in both default-fast and
+// safe mode
 // when the loaded value is actually used (returned and passed to printf).
 
 #include <cstdio>
diff --git a/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp b/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
index 6fdf82f3acaf9..688d09e111f76 100644
--- a/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
+++ b/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
@@ -3,9 +3,10 @@
 // RUN: %clangxx_lowfat -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OOB
 // RUN: %clangxx_lowfat_safe -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OOB
 
-// Baseline: all three modes detect an OOB access whose result IS observable
+// Baseline: both tested modes detect an OOB access whose result is observable.
 // Contrast with mode_diff_pure_call.cpp where a discarded-result call lets
-// Fast eliminate the load before the LowFat pass even sees it.
+// default-fast mode (%clangxx_lowfat) eliminate the load before the LowFat
+// pass even sees it.
 
 #include <cstdio>
 #include <cstdlib>
@@ -16,7 +17,7 @@ int main() {
   char *p = (char *)malloc(16);
 
   // 8-byte (double) OOB read at offset 14: bytes [14, 22) overflow the
-  // 16-byte LowFat slot [0, 16). 
+  // 16-byte LowFat slot [0, 16).
   sink = (char)(*reinterpret_cast<volatile double *>(p + 14));
 
   free(p);
diff --git a/compiler-rt/test/lowfat/mode_diff_pure_call.cpp b/compiler-rt/test/lowfat/mode_diff_pure_call.cpp
index 5f3109ea81d02..3de0279497fb6 100644
--- a/compiler-rt/test/lowfat/mode_diff_pure_call.cpp
+++ b/compiler-rt/test/lowfat/mode_diff_pure_call.cpp
@@ -1,12 +1,14 @@
 // RUN: %clangxx_lowfat -O3 %s -o %t && %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-MISS
 // RUN: %clangxx_lowfat_safe -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-CATCH
 
-// Demonstrates a behavioral difference between Fast mode and Safe/Comprehensive mode.
+// Demonstrates a behavioral difference between default-fast (%clangxx_lowfat)
+// and safe mode (%clangxx_lowfat_safe).
 //
 // The scenario: a noinline function performs an OOB heap read but its return value
 // is discarded by the caller.
 //
-// Fast mode has no PipelineStartEP pass. LLVM's Dead Argument Elimination (DAE)
+// Default-fast mode has no PipelineStartEP pass. LLVM's Dead Argument
+// Elimination (DAE)
 // sees peek()'s return value is unused at all call sites and rewrites:
 //   ret %loaded_val  →  ret undef
 // The load becomes dead and is DCE'd. The LowFat pass at OptimizerLastEP finds
@@ -20,17 +22,15 @@
 //      preventing DAE from marking the return value as dead. The load survives
 //      to OptimizerLastEP where LowFat instruments it → OOB is caught.
 //
-// Comprehensive mode instruments at PipelineStartEP before any optimization,
-// so the check call is inserted before DAE has a chance to remove the load.
-//
 // Expected outputs:
-//   Fast (-O3):          load DCE'd by DAE → no OOB check → program exits 0 → DONE
-//   Safe (-O3):          fake.use keeps load alive → OOB detected → LOWFAT ERROR
+//   default-fast (-O3):  load DCE'd by DAE -> no OOB check -> program exits 0 -> DONE
+//   safe (-O3):          fake.use keeps load alive -> OOB detected -> LOWFAT ERROR
 
 #include <cstdio>
 #include <cstdlib>
 
-// noinline: the optimizer cannot see the body from the call site in Fast mode,
+// noinline: the optimizer cannot see the body from the call site in default-fast
+// mode,
 // so it performs inter-procedural attribute inference rather than inlining.
 __attribute__((noinline))
 static double peek(char *p) {

>From 47ab9c41dc49bb00ac90833db6e13ffc62d99fb7 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 7 Mar 2026 22:11:51 +0800
Subject: [PATCH 40/55] [LowFat] Simplify documentation for tests

---
 compiler-rt/test/lowfat/basic_inbounds.cpp    |  5 ++-
 .../test/lowfat/cross_boundary_oob.cpp        |  8 ++---
 compiler-rt/test/lowfat/dead_load.cpp         |  7 ++--
 compiler-rt/test/lowfat/free_list_reuse.cpp   |  4 +--
 .../test/lowfat/inbounds_no_report.cpp        |  3 +-
 .../lowfat/malloc_intercepted_inbounds.cpp    |  6 ++--
 .../test/lowfat/memcpy_oob_dynamic_fatal.cpp  |  5 ++-
 .../test/lowfat/memcpy_oob_static_fatal.cpp   |  6 ++--
 .../test/lowfat/memcpy_oob_static_recover.cpp |  7 ++--
 .../test/lowfat/memmove_oob_dynamic_fatal.cpp |  6 ++--
 .../test/lowfat/memset_oob_dynamic_fatal.cpp  |  5 ++-
 .../test/lowfat/memset_oob_static_fatal.cpp   |  5 ++-
 .../test/lowfat/memset_oob_static_recover.cpp |  4 +--
 .../test/lowfat/mode_baseline_all_detect.cpp  |  7 ++--
 .../test/lowfat/mode_diff_pure_call.cpp       | 35 ++++---------------
 compiler-rt/test/lowfat/non_pow2_inbounds.cpp |  6 ++--
 compiler-rt/test/lowfat/non_pow2_oob.cpp      |  8 ++---
 .../test/lowfat/non_pow2_oob_boundary.cpp     | 31 +++-------------
 compiler-rt/test/lowfat/oob_read_fatal.cpp    |  7 ++--
 19 files changed, 51 insertions(+), 114 deletions(-)

diff --git a/compiler-rt/test/lowfat/basic_inbounds.cpp b/compiler-rt/test/lowfat/basic_inbounds.cpp
index 6ea912984b57a..4d06c914f7996 100644
--- a/compiler-rt/test/lowfat/basic_inbounds.cpp
+++ b/compiler-rt/test/lowfat/basic_inbounds.cpp
@@ -4,8 +4,7 @@
 // RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
-// Verify that the LowFat runtime initializes and basic in-bounds
-// allocations work without errors.
+// Basic in-bounds allocation test.
 
 #include <cstdio>
 
@@ -17,7 +16,7 @@ int main() {
   if (!arr)
     return 1;
 
-  // In-bounds accesses — should not trigger OOB
+  // In-bounds accesses should not trigger OOB.
   arr[0] = 42;
   arr[9] = 99;
 
diff --git a/compiler-rt/test/lowfat/cross_boundary_oob.cpp b/compiler-rt/test/lowfat/cross_boundary_oob.cpp
index 46f428f3a95f7..6aa172698858d 100644
--- a/compiler-rt/test/lowfat/cross_boundary_oob.cpp
+++ b/compiler-rt/test/lowfat/cross_boundary_oob.cpp
@@ -4,14 +4,12 @@
 // RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
-// Verify that a cross-boundary out-of-bounds access is detected.
-// Writing 8 bytes starting at offset 12 of a 16-byte slot crosses
-// the slot boundary (bytes 12-19 > 16 bytes).
+// Cross-boundary OOB write must be reported.
 
 extern "C" void *__lf_malloc(unsigned long size);
 
 int main() {
-  // Allocate exactly 16 bytes → 16-byte size class (smallest slot)
+  // Allocate exactly 16 bytes.
   char *buf = (char *)__lf_malloc(16);
   if (!buf)
     return 1;
@@ -21,7 +19,7 @@ int main() {
   buf[15] = 'B';
 
   // Cross-boundary OOB: write 8 bytes at offset 12
-  // ptr + 8 = buf+20 > buf+16 → out of bounds
+  // ptr + 8 = buf+20 > buf+16: out of bounds.
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   double *cross = (double *)(buf + 12);
   *cross = 3.14;
diff --git a/compiler-rt/test/lowfat/dead_load.cpp b/compiler-rt/test/lowfat/dead_load.cpp
index 8d4bd70c8c057..b1211008a8c83 100644
--- a/compiler-rt/test/lowfat/dead_load.cpp
+++ b/compiler-rt/test/lowfat/dead_load.cpp
@@ -2,8 +2,7 @@
 // RUN: %clangxx_lowfat_safe -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL
 
 // Verifies that a genuine OOB heap read is detected in both default-fast and
-// safe mode
-// when the loaded value is actually used (returned and passed to printf).
+// safe mode when the loaded value is actually used (returned and printed).
 
 #include <cstdio>
 #include <cstdlib>
@@ -11,13 +10,13 @@
 __attribute__((noinline))
 double oob_read(char *p) {
   // 8-byte read at offset 14 of a 16-byte allocation.
-  // Bytes [14, 22) exceed the slot boundary [0, 16) → genuine OOB.
+  // Bytes [14, 22) exceed the slot boundary [0, 16): genuine OOB.
   return *(double *)(p + 14);
 }
 
 int main() {
   char *p = (char *)malloc(16);
-  double val = oob_read(p);  // return value kept live → load not DCE'd
+  double val = oob_read(p);  // Return value kept live; load is not DCE'd.
   // CHECK-ALL: LOWFAT ERROR: out-of-bounds error detected!
   printf("val=%f\n", val);
   free(p);
diff --git a/compiler-rt/test/lowfat/free_list_reuse.cpp b/compiler-rt/test/lowfat/free_list_reuse.cpp
index 2384eab006362..d1222b1e33845 100644
--- a/compiler-rt/test/lowfat/free_list_reuse.cpp
+++ b/compiler-rt/test/lowfat/free_list_reuse.cpp
@@ -4,7 +4,7 @@
 // RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
-// Verify that allocation and free list reuse works correctly, with no OOB errors.
+// Free-list reuse sanity test.
 
 #include <cstdio>
 
@@ -12,7 +12,7 @@ extern "C" void *__lf_malloc(unsigned long size);
 extern "C" void __lf_free(void *ptr);
 
 int main() {
-  // Allocate and free, then allocate again — should reuse from free list
+  // Allocate and free, then allocate again.
   int *a = (int *)__lf_malloc(10 * sizeof(int));
   a[0] = 1;
   __lf_free(a);
diff --git a/compiler-rt/test/lowfat/inbounds_no_report.cpp b/compiler-rt/test/lowfat/inbounds_no_report.cpp
index 72b0fb07c63ec..b90741ee5e86f 100644
--- a/compiler-rt/test/lowfat/inbounds_no_report.cpp
+++ b/compiler-rt/test/lowfat/inbounds_no_report.cpp
@@ -4,8 +4,7 @@
 // RUN: %clangxx_lowfat -O3 %s -o %t
 // RUN: %run %t 2>&1 | FileCheck %s
 
-// Verify that purely in-bounds accesses — including memcpy and memset within
-// the allocation size — do not trigger any OOB report.
+// In-bounds accesses, memcpy, and memset should not report OOB.
 
 #include <cstdio>
 #include <cstdlib>
diff --git a/compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp b/compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp
index f8359b2d3f45c..6e929bac7c312 100644
--- a/compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp
+++ b/compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp
@@ -1,18 +1,18 @@
 // RUN: %clangxx_lowfat -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
 // RUN: %clangxx_lowfat -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
 
-// Verify that malloc-intercepted allocations are correctly handled for in-bounds accesses.
+// Basic in-bounds test for malloc-intercepted allocations.
 
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 
 int main() {
-  // malloc goes through the LowFat interceptor → produces a LowFat pointer.
+  // malloc goes through the LowFat interceptor and returns a LowFat pointer.
   char *buf = (char *)malloc(32);
   if (!buf) return 1;
 
-  // In-bounds scalar accesses — first and last byte.
+  // In-bounds scalar accesses: first and last byte.
   buf[0] = 'A';
   buf[31] = 'Z';
 
diff --git a/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
index db8c65f25b918..c3505265263a1 100644
--- a/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
+++ b/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
@@ -4,8 +4,7 @@
 // RUN: %clangxx_lowfat -fno-builtin-memcpy -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
-// Verify that memcpy writing past the end of a LowFat allocation is detected
-// in fatal mode, even when the size isn't a compile-time constant.
+// memcpy OOB write with non-constant size must be reported in fatal mode.
 
 #include <string.h>
 #include <stdlib.h>
@@ -18,7 +17,7 @@ int main(int argc, char **argv) {
   // Use argc to prevent the optimizer from knowing the size at compile time.
   // This forces a call to libc memcpy instead of llvm.memcpy.
   size_t size = 16 + argc; // 17
-  
+
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   // CHECK: operation = write
   // CHECK: size      = 16
diff --git a/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp b/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
index c95553fc80c2a..5f914a93726a7 100644
--- a/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
+++ b/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
@@ -1,9 +1,7 @@
 // RUN: %clangxx_lowfat_safe -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
 // RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
 
-// Verify that memcpy writing past the end of a LowFat allocation is detected
-// in fatal mode.  The process must exit with a non-zero code (checked by
-// "not %run") and print the expected diagnostic.
+// memcpy OOB write must be reported in fatal mode.
 
 #include <cstdlib>
 #include <cstring>
@@ -15,7 +13,7 @@ int main() {
 
   const char payload[32] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
 
-  // memcpy of 32 bytes into a 16-byte allocation — overflows by 16 bytes.
+  // memcpy of 32 bytes into a 16-byte allocation overflows by 16 bytes.
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   memcpy(dst, payload, 32);
 
diff --git a/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp b/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
index 62316bf1a1a0e..7b7bcdbf51ff8 100644
--- a/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
+++ b/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
@@ -3,10 +3,7 @@
 // RUN: %clangxx_lowfat_safe_recover -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
 // RUN: %clangxx_lowfat_safe_recover -O3 %s -o %t && %run %t 2>&1 | FileCheck %s
 
-// Verify that memcpy OOB in recover mode:
-//   1. Prints a WARNING (not ERROR).
-//   2. Execution continues past the call (process exits 0).
-//   3. The reported overflow is positive (access end past allocation end).
+// memcpy OOB write in recover mode must warn and continue.
 
 #include <cstdio>
 #include <cstdlib>
@@ -22,7 +19,7 @@ int main() {
   // CHECK: LOWFAT WARNING: out-of-bounds error detected!
   memcpy(dst, payload, 32);
 
-  // Execution must reach here in recover mode.
+  // Execution should continue in recover mode.
   // CHECK: after memcpy
   printf("after memcpy\n");
 
diff --git a/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
index 43b23a8ffca20..995522deec07a 100644
--- a/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
+++ b/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
@@ -4,7 +4,7 @@
 // RUN: %clangxx_lowfat -fno-builtin-memmove -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
-// Verify that memmove writing past the end of a LowFat allocation is detected
+// memmove OOB read with non-constant size must be reported.
 
 #include <string.h>
 #include <stdlib.h>
@@ -15,8 +15,8 @@ int main(int argc, char **argv) {
   if (!dst) return 1;
 
   // Use argc to prevent the optimizer from knowing the size at compile time.
-  size_t size = 12 + argc; // 13. dst+13 is within 16, but src+13 = dst+17, which is OOB (+1)
-  
+  size_t size = 12 + argc; // 13. dst+13 is within 16, but src+13 = dst+17 (+1 OOB)
+
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   // CHECK: operation = read
   // CHECK: size      = 16
diff --git a/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
index 2f78d67e7577b..9f66e06b84b92 100644
--- a/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
@@ -4,8 +4,7 @@
 // RUN: %clangxx_lowfat -fno-builtin-memset -O3 %s -o %t
 // RUN: not %run %t 2>&1 | FileCheck %s
 
-// Verify that memset writing past the end of a LowFat allocation is detected
-// in fatal mode, even when the size isn't a compile-time constant.
+// memset OOB write with non-constant size must be reported in fatal mode.
 
 #include <string.h>
 #include <stdlib.h>
@@ -18,7 +17,7 @@ int main(int argc, char **argv) {
   // This forces a call to libc memset instead of llvm.memset, which proves
   // our runtime interceptor handles it.
   size_t size = 16 + argc; // 17
-  
+
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   // CHECK: operation = write
   // CHECK: size      = 16
diff --git a/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp b/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
index 638c7a57dee07..28c0366e4c245 100644
--- a/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
@@ -1,8 +1,7 @@
 // RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
 // RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
 
-// Verify that memset writing past the end of a LowFat allocation is detected
-// in fatal mode.
+// memset OOB write must be reported in fatal mode.
 
 #include <cstdlib>
 #include <cstring>
@@ -12,7 +11,7 @@ int main() {
   char *guard = (char *)malloc(16); // keep adjacent memory mapped
   if (!dst || !guard) return 1;
 
-  // memset of 32 bytes into a 16-byte allocation — overflows by 16 bytes.
+  // memset of 32 bytes into a 16-byte allocation overflows by 16 bytes.
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   memset(dst, 0, 32);
 
diff --git a/compiler-rt/test/lowfat/memset_oob_static_recover.cpp b/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
index c5f3d5b2702c2..ac31a352e6bf7 100644
--- a/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
+++ b/compiler-rt/test/lowfat/memset_oob_static_recover.cpp
@@ -3,7 +3,7 @@
 // RUN: %clangxx_lowfat_safe_recover -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
 // RUN: %clangxx_lowfat_safe_recover -O3 %s -o %t && %run %t 2>&1 | FileCheck %s
 
-// Verify that memset OOB in recover mode warns and continues.
+// memset OOB write in recover mode must warn and continue.
 
 #include <cstdio>
 #include <cstdlib>
@@ -14,7 +14,7 @@ int main() {
   char *guard = (char *)malloc(16);
   if (!dst || !guard) return 1;
 
-  // memset of 32 bytes into a 16-byte allocation — overflows by 16 bytes.
+  // memset of 32 bytes into a 16-byte allocation overflows by 16 bytes.
   // CHECK: LOWFAT WARNING: out-of-bounds error detected!
   memset(dst, 0, 32);
 
diff --git a/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp b/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
index 688d09e111f76..f547789b7fe83 100644
--- a/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
+++ b/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
@@ -3,10 +3,9 @@
 // RUN: %clangxx_lowfat -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OOB
 // RUN: %clangxx_lowfat_safe -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OOB
 
-// Baseline: both tested modes detect an OOB access whose result is observable.
-// Contrast with mode_diff_pure_call.cpp where a discarded-result call lets
-// default-fast mode (%clangxx_lowfat) eliminate the load before the LowFat
-// pass even sees it.
+// Baseline: both tested modes detect this OOB read because the result is used.
+// Contrast with mode_diff_pure_call.cpp, where default-fast can remove a dead
+// load before instrumentation.
 
 #include <cstdio>
 #include <cstdlib>
diff --git a/compiler-rt/test/lowfat/mode_diff_pure_call.cpp b/compiler-rt/test/lowfat/mode_diff_pure_call.cpp
index 3de0279497fb6..87b8972b033cc 100644
--- a/compiler-rt/test/lowfat/mode_diff_pure_call.cpp
+++ b/compiler-rt/test/lowfat/mode_diff_pure_call.cpp
@@ -1,37 +1,16 @@
 // RUN: %clangxx_lowfat -O3 %s -o %t && %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-MISS
 // RUN: %clangxx_lowfat_safe -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-CATCH
 
-// Demonstrates a behavioral difference between default-fast (%clangxx_lowfat)
-// and safe mode (%clangxx_lowfat_safe).
-//
-// The scenario: a noinline function performs an OOB heap read but its return value
-// is discarded by the caller.
-//
-// Default-fast mode has no PipelineStartEP pass. LLVM's Dead Argument
-// Elimination (DAE)
-// sees peek()'s return value is unused at all call sites and rewrites:
-//   ret %loaded_val  →  ret undef
-// The load becomes dead and is DCE'd. The LowFat pass at OptimizerLastEP finds
-// no load to instrument — the OOB is missed.
-//
-// Safe mode's barrier pass runs at PipelineStartEP and inserts:
-//   1. @llvm.sideeffect() — prevents call-level DCE by blocking memory(none)
-//      inference on peek().
-//   2. @llvm.fake.use(loaded_val) — after every load. fake.use creates a data
-//      dependency on the loaded value without emitting machine code,
-//      preventing DAE from marking the return value as dead. The load survives
-//      to OptimizerLastEP where LowFat instruments it → OOB is caught.
-//
-// Expected outputs:
-//   default-fast (-O3):  load DCE'd by DAE -> no OOB check -> program exits 0 -> DONE
-//   safe (-O3):          fake.use keeps load alive -> OOB detected -> LOWFAT ERROR
+// Mode-difference test for discarded return values at -O3.
+// In default-fast (%clangxx_lowfat), DAE can remove the load before
+// OptimizerLastEP instrumentation, so the OOB read is missed.
+// In safe mode (%clangxx_lowfat_safe), the PipelineStartEP barrier/fake-use
+// keeps the load alive and the OOB read is reported.
 
 #include <cstdio>
 #include <cstdlib>
 
-// noinline: the optimizer cannot see the body from the call site in default-fast
-// mode,
-// so it performs inter-procedural attribute inference rather than inlining.
+// noinline keeps this as an inter-procedural case.
 __attribute__((noinline))
 static double peek(char *p) {
   // 8-byte (double) OOB read. p was allocated with malloc(16); a double starting
@@ -42,7 +21,7 @@ static double peek(char *p) {
 
 int main() {
   char *p = (char *)malloc(16);
-  peek(p);   // Return value discarded — caller has no use for it.
+  peek(p);   // Return value intentionally discarded.
   free(p);
 
   // CHECK-MISS: DONE
diff --git a/compiler-rt/test/lowfat/non_pow2_inbounds.cpp b/compiler-rt/test/lowfat/non_pow2_inbounds.cpp
index 633aeeb9f1e38..cbb1f54ff5885 100644
--- a/compiler-rt/test/lowfat/non_pow2_inbounds.cpp
+++ b/compiler-rt/test/lowfat/non_pow2_inbounds.cpp
@@ -1,7 +1,6 @@
 // RUN: %clangxx_lowfat -O0 %s -o %t && %run %t
 
-// Verify that an in-bounds write to the last byte of a 48-byte allocation
-// does not trigger a false positive OOB error.
+// In-bounds write at the last byte of a 48-byte allocation should not report OOB.
 //
 // REQUIRES: lowfat-custom-config
 
@@ -11,8 +10,7 @@ int main() {
   char *p = (char *)malloc(48);
   if (!p) return 1;
 
-  // Write to the very last byte of the 48-byte allocation.
-  // This must NOT trigger a LowFat error.
+  // Write to the last byte. This must not report a LowFat error.
   p[47] = 'x';
 
   free(p);
diff --git a/compiler-rt/test/lowfat/non_pow2_oob.cpp b/compiler-rt/test/lowfat/non_pow2_oob.cpp
index a79245447d67e..789e4a06acd70 100644
--- a/compiler-rt/test/lowfat/non_pow2_oob.cpp
+++ b/compiler-rt/test/lowfat/non_pow2_oob.cpp
@@ -1,9 +1,8 @@
 // RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
 // RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
 
-// Verify that an OOB write 2 bytes past a 48-byte allocation is detected.
-// This test exercises the non-POW2 magic-multiply path: without custom config
-// the allocation would be silently rounded to 64 bytes, making p[50] valid.
+// OOB write past a 48-byte allocation must be reported.
+// This exercises the non-pow2 magic-multiply path.
 //
 // REQUIRES: lowfat-custom-config
 
@@ -14,8 +13,7 @@ int main() {
   if (!p) return 1;
 
   // Write 8 bytes starting at offset 44.
-  // Bytes 44-51 will cross the 48-byte allocation boundary, straddling
-  // into the next object grid. This triggers the OOB error.
+  // Bytes 44-51 cross the 48-byte allocation boundary.
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   double *val = (double *)(p + 44);
   *val = 1.0;
diff --git a/compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp b/compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp
index 23fd8bee42568..33c704b95cbc1 100644
--- a/compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp
+++ b/compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp
@@ -1,36 +1,14 @@
 // RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s
 // RUN: %clangxx_lowfat_safe -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
 
-// Demonstrates GEP-level (pointer arithmetic) instrumentation catching an OOB
-// pointer that escapes to a neighbouring allocation slot — a case where
-// load/store checking alone produces a false negative.
-//
-// SCENARIO
-// --------
-// p is a 48-byte LowFat allocation at address k*48 (absolute-zero aligned).
-// p+48 = (k+1)*48 is the exact start of the NEXT 48-byte slot.
-//
-//   Load/store check on *(p+48):
-//     GetBase(p+48) = (k+1)*48 = p+48  <-- attributed to NEXT slot
-//     End           = p+48 + 48 = p+96
-//     AccessEnd     = p+48 + 1  = p+49
-//     p+49 <= p+96  --> NOT OOB  (false negative — sink() would not catch it)
-//
-//   GEP check at the arithmetic (p + 48) itself:
-//     GetBase(p)    = k*48 = p           <-- uses SOURCE pointer's bounds
-//     End           = p + 48
-//     result p+48   >= End               --> OOB (detected before escape!)
-//
-// By checking at the point of pointer arithmetic, the error is caught before
-// the OOB pointer reaches sink(), regardless of which slot it happens to land in.
+// GEP-level instrumentation test: p + 48 is one-past a 48-byte object and must
+// be reported before the pointer escapes to sink().
 //
 // REQUIRES: lowfat-custom-config
 
 #include <stdlib.h>
 
-// Prevent inlining so the OOB pointer is forced to cross a function boundary,
-// making the "escape" explicit. The store inside sink() is NOT detectable by
-// load/store checking alone (GetBase(q) = q, so it appears in-bounds).
+// noinline keeps this as a cross-function pointer-escape case.
 __attribute__((noinline))
 static void sink(volatile char *q) { *q = 'x'; }
 
@@ -38,8 +16,7 @@ int main() {
   char *p = (char *)malloc(48);
   if (!p) return 1;
 
-  // GEP check: p+48 is computed using p's bounds (End = p+48).
-  // result == End  -->  OOB detected here, before the pointer reaches sink().
+  // GEP check: result == End, so this is out of bounds.
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   sink(p + 48);
 
diff --git a/compiler-rt/test/lowfat/oob_read_fatal.cpp b/compiler-rt/test/lowfat/oob_read_fatal.cpp
index 290f450ad626d..055bc5422ffe4 100644
--- a/compiler-rt/test/lowfat/oob_read_fatal.cpp
+++ b/compiler-rt/test/lowfat/oob_read_fatal.cpp
@@ -3,8 +3,7 @@
 // RUN: %clangxx_lowfat_safe -O2 %s -o %t && not %run %t 2>&1 | FileCheck %s
 // RUN: %clangxx_lowfat_safe -O3 %s -o %t && not %run %t 2>&1 | FileCheck %s
 
-// Verify that an OOB load (scalar read across allocation boundary) is detected
-// in fatal mode across all optimisation levels and modes.
+// OOB scalar read across an allocation boundary must be reported in fatal mode.
 
 #include <cstdlib>
 
@@ -16,10 +15,10 @@ int main() {
   buf[31] = 'i';
 
   // Read 8 bytes at offset 28 of a 32-byte allocation:
-  //   bytes 28–35 exceed the 32-byte boundary → OOB.
+  //   bytes 28-35 exceed the 32-byte boundary: OOB.
   // CHECK: LOWFAT ERROR: out-of-bounds error detected!
   double *p = (double *)(buf + 28);
-  double val = *p; // 8-byte read at offset 28 of 32-byte alloc → OOB (bytes 28–35)
+  double val = *p; // 8-byte read at offset 28 of 32-byte alloc: OOB (bytes 28-35)
   (void)val;       // keep live to prevent DSE; crash fires on the load above
 
   free(buf);

>From acd4559b86f402acb5d6045438ac44e56d83b266 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sat, 7 Mar 2026 22:18:39 +0800
Subject: [PATCH 41/55] [LowFat] Refactor tests into folders

---
 .../test/lowfat/{ => TestCases/inbounds}/basic_inbounds.cpp       | 0
 .../test/lowfat/{ => TestCases/inbounds}/free_list_reuse.cpp      | 0
 .../test/lowfat/{ => TestCases/inbounds}/inbounds_no_report.cpp   | 0
 .../{ => TestCases/inbounds}/malloc_intercepted_inbounds.cpp      | 0
 .../{ => TestCases/memintrinsics}/memcpy_oob_dynamic_fatal.cpp    | 0
 .../{ => TestCases/memintrinsics}/memcpy_oob_static_fatal.cpp     | 0
 .../{ => TestCases/memintrinsics}/memcpy_oob_static_recover.cpp   | 0
 .../{ => TestCases/memintrinsics}/memmove_oob_dynamic_fatal.cpp   | 0
 .../{ => TestCases/memintrinsics}/memset_oob_dynamic_fatal.cpp    | 0
 .../{ => TestCases/memintrinsics}/memset_oob_static_fatal.cpp     | 0
 .../{ => TestCases/memintrinsics}/memset_oob_static_recover.cpp   | 0
 compiler-rt/test/lowfat/{ => TestCases/mode}/dead_load.cpp        | 0
 .../test/lowfat/{ => TestCases/mode}/mode_baseline_all_detect.cpp | 0
 .../test/lowfat/{ => TestCases/mode}/mode_diff_pure_call.cpp      | 0
 .../test/lowfat/{ => TestCases/non_pow2}/non_pow2_inbounds.cpp    | 0
 compiler-rt/test/lowfat/{ => TestCases/non_pow2}/non_pow2_oob.cpp | 0
 .../lowfat/{ => TestCases/non_pow2}/non_pow2_oob_boundary.cpp     | 0
 .../test/lowfat/{ => TestCases/oob}/cross_boundary_oob.cpp        | 0
 compiler-rt/test/lowfat/{ => TestCases/oob}/oob_read_fatal.cpp    | 0
 19 files changed, 0 insertions(+), 0 deletions(-)
 rename compiler-rt/test/lowfat/{ => TestCases/inbounds}/basic_inbounds.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/inbounds}/free_list_reuse.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/inbounds}/inbounds_no_report.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/inbounds}/malloc_intercepted_inbounds.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/memintrinsics}/memcpy_oob_dynamic_fatal.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/memintrinsics}/memcpy_oob_static_fatal.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/memintrinsics}/memcpy_oob_static_recover.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/memintrinsics}/memmove_oob_dynamic_fatal.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/memintrinsics}/memset_oob_dynamic_fatal.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/memintrinsics}/memset_oob_static_fatal.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/memintrinsics}/memset_oob_static_recover.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/mode}/dead_load.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/mode}/mode_baseline_all_detect.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/mode}/mode_diff_pure_call.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/non_pow2}/non_pow2_inbounds.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/non_pow2}/non_pow2_oob.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/non_pow2}/non_pow2_oob_boundary.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/oob}/cross_boundary_oob.cpp (100%)
 rename compiler-rt/test/lowfat/{ => TestCases/oob}/oob_read_fatal.cpp (100%)

diff --git a/compiler-rt/test/lowfat/basic_inbounds.cpp b/compiler-rt/test/lowfat/TestCases/inbounds/basic_inbounds.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/basic_inbounds.cpp
rename to compiler-rt/test/lowfat/TestCases/inbounds/basic_inbounds.cpp
diff --git a/compiler-rt/test/lowfat/free_list_reuse.cpp b/compiler-rt/test/lowfat/TestCases/inbounds/free_list_reuse.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/free_list_reuse.cpp
rename to compiler-rt/test/lowfat/TestCases/inbounds/free_list_reuse.cpp
diff --git a/compiler-rt/test/lowfat/inbounds_no_report.cpp b/compiler-rt/test/lowfat/TestCases/inbounds/inbounds_no_report.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/inbounds_no_report.cpp
rename to compiler-rt/test/lowfat/TestCases/inbounds/inbounds_no_report.cpp
diff --git a/compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp b/compiler-rt/test/lowfat/TestCases/inbounds/malloc_intercepted_inbounds.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/malloc_intercepted_inbounds.cpp
rename to compiler-rt/test/lowfat/TestCases/inbounds/malloc_intercepted_inbounds.cpp
diff --git a/compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/TestCases/memintrinsics/memcpy_oob_dynamic_fatal.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memcpy_oob_dynamic_fatal.cpp
rename to compiler-rt/test/lowfat/TestCases/memintrinsics/memcpy_oob_dynamic_fatal.cpp
diff --git a/compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp b/compiler-rt/test/lowfat/TestCases/memintrinsics/memcpy_oob_static_fatal.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memcpy_oob_static_fatal.cpp
rename to compiler-rt/test/lowfat/TestCases/memintrinsics/memcpy_oob_static_fatal.cpp
diff --git a/compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp b/compiler-rt/test/lowfat/TestCases/memintrinsics/memcpy_oob_static_recover.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memcpy_oob_static_recover.cpp
rename to compiler-rt/test/lowfat/TestCases/memintrinsics/memcpy_oob_static_recover.cpp
diff --git a/compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/TestCases/memintrinsics/memmove_oob_dynamic_fatal.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memmove_oob_dynamic_fatal.cpp
rename to compiler-rt/test/lowfat/TestCases/memintrinsics/memmove_oob_dynamic_fatal.cpp
diff --git a/compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp b/compiler-rt/test/lowfat/TestCases/memintrinsics/memset_oob_dynamic_fatal.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memset_oob_dynamic_fatal.cpp
rename to compiler-rt/test/lowfat/TestCases/memintrinsics/memset_oob_dynamic_fatal.cpp
diff --git a/compiler-rt/test/lowfat/memset_oob_static_fatal.cpp b/compiler-rt/test/lowfat/TestCases/memintrinsics/memset_oob_static_fatal.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memset_oob_static_fatal.cpp
rename to compiler-rt/test/lowfat/TestCases/memintrinsics/memset_oob_static_fatal.cpp
diff --git a/compiler-rt/test/lowfat/memset_oob_static_recover.cpp b/compiler-rt/test/lowfat/TestCases/memintrinsics/memset_oob_static_recover.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/memset_oob_static_recover.cpp
rename to compiler-rt/test/lowfat/TestCases/memintrinsics/memset_oob_static_recover.cpp
diff --git a/compiler-rt/test/lowfat/dead_load.cpp b/compiler-rt/test/lowfat/TestCases/mode/dead_load.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/dead_load.cpp
rename to compiler-rt/test/lowfat/TestCases/mode/dead_load.cpp
diff --git a/compiler-rt/test/lowfat/mode_baseline_all_detect.cpp b/compiler-rt/test/lowfat/TestCases/mode/mode_baseline_all_detect.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/mode_baseline_all_detect.cpp
rename to compiler-rt/test/lowfat/TestCases/mode/mode_baseline_all_detect.cpp
diff --git a/compiler-rt/test/lowfat/mode_diff_pure_call.cpp b/compiler-rt/test/lowfat/TestCases/mode/mode_diff_pure_call.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/mode_diff_pure_call.cpp
rename to compiler-rt/test/lowfat/TestCases/mode/mode_diff_pure_call.cpp
diff --git a/compiler-rt/test/lowfat/non_pow2_inbounds.cpp b/compiler-rt/test/lowfat/TestCases/non_pow2/non_pow2_inbounds.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/non_pow2_inbounds.cpp
rename to compiler-rt/test/lowfat/TestCases/non_pow2/non_pow2_inbounds.cpp
diff --git a/compiler-rt/test/lowfat/non_pow2_oob.cpp b/compiler-rt/test/lowfat/TestCases/non_pow2/non_pow2_oob.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/non_pow2_oob.cpp
rename to compiler-rt/test/lowfat/TestCases/non_pow2/non_pow2_oob.cpp
diff --git a/compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp b/compiler-rt/test/lowfat/TestCases/non_pow2/non_pow2_oob_boundary.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/non_pow2_oob_boundary.cpp
rename to compiler-rt/test/lowfat/TestCases/non_pow2/non_pow2_oob_boundary.cpp
diff --git a/compiler-rt/test/lowfat/cross_boundary_oob.cpp b/compiler-rt/test/lowfat/TestCases/oob/cross_boundary_oob.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/cross_boundary_oob.cpp
rename to compiler-rt/test/lowfat/TestCases/oob/cross_boundary_oob.cpp
diff --git a/compiler-rt/test/lowfat/oob_read_fatal.cpp b/compiler-rt/test/lowfat/TestCases/oob/oob_read_fatal.cpp
similarity index 100%
rename from compiler-rt/test/lowfat/oob_read_fatal.cpp
rename to compiler-rt/test/lowfat/TestCases/oob/oob_read_fatal.cpp

>From d01af7a9b272274839b45a4cb709acc93e9748ce Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sun, 8 Mar 2026 16:25:53 +0800
Subject: [PATCH 42/55] [LowFat] Add tmp initialization script for building and
 testing LowFat

---
 configure_llvm.sh | 28 ++++++++++++++++++++++++++++
 run_lowfat.sh     | 24 ++++++++++++++++++++++++
 2 files changed, 52 insertions(+)
 create mode 100755 configure_llvm.sh
 create mode 100755 run_lowfat.sh

diff --git a/configure_llvm.sh b/configure_llvm.sh
new file mode 100755
index 0000000000000..f8a831eff15f3
--- /dev/null
+++ b/configure_llvm.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+CMAKE_ARGS=(
+    -G Ninja -S llvm -B build
+    -DCMAKE_BUILD_TYPE=RelWithDebInfo
+    -DLLVM_ENABLE_ASSERTIONS=ON
+    -DLLVM_ENABLE_PROJECTS="clang;lld"
+    -DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind"
+    -DCLANG_DEFAULT_RTLIB=compiler-rt
+    -DCLANG_DEFAULT_LINKER=lld
+    -DLLVM_CCACHE_BUILD=ON
+    -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
+)
+
+if [ "$(uname)" == "Darwin" ]; then # macOS-specific
+    CMAKE_ARGS+=(
+        -DDEFAULT_SYSROOT="$(xcrun --show-sdk-path)"
+        -DCLANG_DEFAULT_CXX_STDLIB=libc++
+    )
+else # Ubuntu & compute cluster (Linux)
+    CMAKE_ARGS+=(
+        -DCLANG_DEFAULT_UNWINDLIB=libgcc
+        -DCLANG_DEFAULT_CXX_STDLIB=libstdc++
+        -DBUILD_SHARED_LIBS=ON
+    )
+fi
+
+cmake "${CMAKE_ARGS[@]}"
diff --git a/run_lowfat.sh b/run_lowfat.sh
new file mode 100755
index 0000000000000..791ad934d5560
--- /dev/null
+++ b/run_lowfat.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+# Toggle configuration based on argument
+if [[ "$1" == "custom" ]]; then
+    echo "[+] Configuring custom LowFat sizes..."
+    cmake -DLOWFAT_SIZES_CFG="$PWD/compiler-rt/lib/lowfat/tools/sizes.cfg" build
+elif [[ "$1" == "pow2" ]]; then
+    echo "[+] Configuring default pow2 LowFat sizes..."
+    cmake -ULOWFAT_SIZES_CFG build
+else
+    echo "Usage: $0 [custom|pow2]"
+    exit 1
+fi
+
+# Symlink compile_commands.json for LSP
+echo "[+] Updating compile_commands.json symlink..."
+ln -sf build/compile_commands.json .
+
+# Build and run tests
+echo "[+] Building..."
+ninja -C build
+
+echo "[+] Running LowFat tests..."
+ninja -C build/runtimes/runtimes-bins check-lowfat

>From c185eaf8470c58ea52f0aea4fe2fcbf4cd339e54 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Sun, 8 Mar 2026 16:36:45 +0800
Subject: [PATCH 43/55] [LowFat] Optimize initialization script for llvm config

---
 configure_llvm.sh | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/configure_llvm.sh b/configure_llvm.sh
index f8a831eff15f3..adf99aa539112 100755
--- a/configure_llvm.sh
+++ b/configure_llvm.sh
@@ -1,5 +1,6 @@
 #!/usr/bin/env bash
 
+# Common flags for all systems
 CMAKE_ARGS=(
     -G Ninja -S llvm -B build
     -DCMAKE_BUILD_TYPE=RelWithDebInfo
@@ -9,6 +10,8 @@ CMAKE_ARGS=(
     -DCLANG_DEFAULT_RTLIB=compiler-rt
     -DCLANG_DEFAULT_LINKER=lld
     -DLLVM_CCACHE_BUILD=ON
+    -DLLVM_TARGETS_TO_BUILD=Native
+    -DLLVM_OPTIMIZED_TABLEGEN=ON
     -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
 )
 
@@ -22,7 +25,9 @@ else # Ubuntu & compute cluster (Linux)
         -DCLANG_DEFAULT_UNWINDLIB=libgcc
         -DCLANG_DEFAULT_CXX_STDLIB=libstdc++
         -DBUILD_SHARED_LIBS=ON
+        -DLLVM_USE_SPLIT_DWARF=ON
     )
 fi
 
+echo "[+] Generating CMake configuration..."
 cmake "${CMAKE_ARGS[@]}"

>From 28f012f0bcdaffdaccd6b3f6e430a41900ee6bcb Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Mon, 9 Mar 2026 12:11:39 +0800
Subject: [PATCH 44/55] [LowFat] Add __lf_get_offset and __lf_get_usable_size

---
 compiler-rt/lib/lowfat/lf_config.h    |  2 +-
 compiler-rt/lib/lowfat/lf_interface.h | 10 ++++++++-
 compiler-rt/lib/lowfat/lf_rtl.cpp     | 29 ++++++++++++++++++++++++---
 3 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_config.h b/compiler-rt/lib/lowfat/lf_config.h
index a3451ce2cd6a0..244cd3d45f1ec 100644
--- a/compiler-rt/lib/lowfat/lf_config.h
+++ b/compiler-rt/lib/lowfat/lf_config.h
@@ -143,7 +143,7 @@ inline bool IsLowFatPointer(uptr ptr) {
 inline uptr GetSize(uptr ptr) {
   uptr region = GetRegionIndex(ptr);
   if (region >= kNumSizeClasses)
-    return 0;  // Not a valid LowFat pointer
+    return (uptr)-1;  // Wide-bounds for non-LowFat pointers
   return SizeClassToSize(region);
 }
 
diff --git a/compiler-rt/lib/lowfat/lf_interface.h b/compiler-rt/lib/lowfat/lf_interface.h
index a3691987ed247..b6840c4ef3e13 100644
--- a/compiler-rt/lib/lowfat/lf_interface.h
+++ b/compiler-rt/lib/lowfat/lf_interface.h
@@ -49,9 +49,17 @@ SANITIZER_INTERFACE_ATTRIBUTE void __lf_warn_oob(uptr ptr, uptr base,
 SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_base(uptr ptr);
 
 // Get the size (bound) of an allocation from a pointer.
-// Returns the allocation size, or 0 if the pointer is not within a LowFat region.
+// Returns the allocation size, or (uptr)-1 if the pointer is not within a LowFat region.
 SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_size(uptr ptr);
 
+// Get the offset from the base address of an allocation.
+// Returns the offset, or 0 if the pointer is not within a LowFat region.
+SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_offset(uptr ptr);
+
+// Get the remaining usable size in the allocation from a pointer.
+// Returns (size - offset), or (uptr)-1 if the pointer is not within a LowFat region.
+SANITIZER_INTERFACE_ATTRIBUTE uptr __lf_get_usable_size(uptr ptr);
+
 // Allocate/Deallocate from LowFat regions.
 SANITIZER_INTERFACE_ATTRIBUTE void *__lf_malloc(uptr size);
 SANITIZER_INTERFACE_ATTRIBUTE void __lf_free(void *ptr);
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index b7d0b37e1450f..5780264a5b29d 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -18,6 +18,7 @@
 #include "lf_config.h"
 #include "lf_interface.h"
 #include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_allocator_internal.h"
 #include "sanitizer_common/sanitizer_flag_parser.h"
 #include "sanitizer_common/sanitizer_flags.h"
 #include "sanitizer_common/sanitizer_mutex.h"
@@ -157,8 +158,11 @@ void *Allocate(uptr size) {
   uptr region_end = GetRegionStart(class_index) + kRegionSize;
   uptr addr = region_next_alloc[class_index];
 
-  if (addr + alloc_size > region_end)
-    return nullptr;
+  if (addr + alloc_size > region_end) {
+    // Region exhausted: fall back to standard libc-style allocation.
+    // The resulting pointer will not be a LowFat pointer (wide-bounds).
+    return (void *)InternalAlloc(size);
+  }
 
   region_next_alloc[class_index] = addr + alloc_size;
   return (void *)addr;
@@ -173,8 +177,10 @@ void Deallocate(void *ptr) {
   uptr addr = (uptr)ptr;
 
   // Validate this is a LowFat pointer
-  if (!IsLowFatPointer(addr))
+  if (!IsLowFatPointer(addr)) {
+    InternalFree(ptr);
     return;
+  }
 
   uptr region = GetRegionIndex(addr);
 
@@ -260,6 +266,23 @@ uptr __lf_get_size(uptr ptr) {
   return __lowfat::GetSize(ptr);
 }
 
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __lf_get_offset(uptr ptr) {
+  uptr base = __lowfat::GetBase(ptr);
+  if (base == 0)
+    return 0;
+  return ptr - base;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __lf_get_usable_size(uptr ptr) {
+  uptr base = __lowfat::GetBase(ptr);
+  uptr size = __lowfat::GetSize(ptr);
+  if (base == 0)
+    return (uptr)-1;
+  return size - (ptr - base);
+}
+
 SANITIZER_INTERFACE_ATTRIBUTE
 void *__lf_malloc(uptr size) {
   return __lowfat::Allocate(size);

>From ec5d9c5dde5ce247c4fdfa312d8c6922235e2a79 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Mon, 9 Mar 2026 12:13:19 +0800
Subject: [PATCH 45/55] [LowFat] Create more tests for oob, underflow

---
 .../lowfat/TestCases/api_reconstruction.cpp   | 74 +++++++++++++++++++
 .../test/lowfat/TestCases/layout_heap.cpp     | 60 +++++++++++++++
 .../lowfat/TestCases/security_core_gep.cpp    | 31 ++++++++
 .../lowfat/TestCases/security_core_oob.cpp    | 32 ++++++++
 .../TestCases/security_core_padding.cpp       | 28 +++++++
 5 files changed, 225 insertions(+)
 create mode 100644 compiler-rt/test/lowfat/TestCases/api_reconstruction.cpp
 create mode 100644 compiler-rt/test/lowfat/TestCases/layout_heap.cpp
 create mode 100644 compiler-rt/test/lowfat/TestCases/security_core_gep.cpp
 create mode 100644 compiler-rt/test/lowfat/TestCases/security_core_oob.cpp
 create mode 100644 compiler-rt/test/lowfat/TestCases/security_core_padding.cpp

diff --git a/compiler-rt/test/lowfat/TestCases/api_reconstruction.cpp b/compiler-rt/test/lowfat/TestCases/api_reconstruction.cpp
new file mode 100644
index 0000000000000..5c50f9ec4cc37
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/api_reconstruction.cpp
@@ -0,0 +1,74 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <assert.h>
+
+typedef uintptr_t uptr;
+
+extern "C" {
+uptr __lf_get_base(uptr ptr);
+uptr __lf_get_size(uptr ptr);
+uptr __lf_get_offset(uptr ptr);
+uptr __lf_get_usable_size(uptr ptr);
+}
+
+#define lowfat_base(p)        __lf_get_base((uptr)(p))
+#define lowfat_size(p)        __lf_get_size((uptr)(p))
+#define lowfat_offset(p)      __lf_get_offset((uptr)(p))
+#define lowfat_usable_size(p) __lf_get_usable_size((uptr)(p))
+
+int main() {
+  size_t request_size = 100;
+  char *p = (char *)malloc(request_size);
+  uptr base = lowfat_base(p);
+  uptr size = lowfat_size(p);
+
+  printf("Base and Interior Pointers:\n");
+  // Test start of object
+  if (lowfat_base(p) == (uptr)p) printf("  start: ok\n");
+  // Test interior pointer
+  if (lowfat_base(p + 50) == (uptr)p) printf("  interior: ok\n");
+
+  printf("Size Operations:\n");
+  // Size should be >= requested size and usually a power of 2 (or from config)
+  if (size >= request_size) printf("  size_ge: ok\n");
+
+  printf("Usable Size & Offsets:\n");
+  if (lowfat_offset(p) == 0) printf("  offset_0: ok\n");
+  if (lowfat_offset(p + 50) == 50) printf("  offset_50: ok\n");
+  if (lowfat_usable_size(p) == size) printf("  usable_start: ok\n");
+  if (lowfat_usable_size(p + 50) == size - 50) printf("  usable_50: ok\n");
+
+  printf("Non-Fat Pointer Handling:\n");
+  // Standard stack pointer (not instrumented yet in this test)
+  int x;
+  if (lowfat_base(&x) == 0) printf("  stack_base: ok\n");
+  if (lowfat_size(&x) == (uptr)-1) printf("  stack_size: ok\n");
+
+  // NULL pointer
+  if (lowfat_base(NULL) == 0) printf("  null_base: ok\n");
+  if (lowfat_size(NULL) == (uptr)-1) printf("  null_size: ok\n");
+
+  free(p);
+  
+  // CHECK: Base and Interior Pointers:
+  // CHECK:   start: ok
+  // CHECK:   interior: ok
+  // CHECK: Size Operations:
+  // CHECK:   size_ge: ok
+  // CHECK: Usable Size & Offsets:
+  // CHECK:   offset_0: ok
+  // CHECK:   offset_50: ok
+  // CHECK:   usable_start: ok
+  // CHECK:   usable_50: ok
+  // CHECK: Non-Fat Pointer Handling:
+  // CHECK:   stack_base: ok
+  // CHECK:   stack_size: ok
+  // CHECK:   null_base: ok
+  // CHECK:   null_size: ok
+
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/TestCases/layout_heap.cpp b/compiler-rt/test/lowfat/TestCases/layout_heap.cpp
new file mode 100644
index 0000000000000..ddbbac03dca29
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/layout_heap.cpp
@@ -0,0 +1,60 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+typedef uintptr_t uptr;
+extern "C" uptr __lf_get_size(uptr ptr);
+extern "C" uptr __lf_get_base(uptr ptr);
+
+int main() {
+  printf("Heap Allocation (Rounding & Alignment):\n");
+  
+  // 1. Exact power of 2
+  void *p16 = malloc(16);
+  if (((uptr)p16 % 16) == 0 && __lf_get_size((uptr)p16) == 16)
+    printf("  16: ok\n");
+
+  // 2. Rounding up (17 -> 32)
+  void *p17 = malloc(17);
+  uptr s17 = __lf_get_size((uptr)p17);
+  if (((uptr)p17 % s17) == 0 && s17 == 32)
+    printf("  17: ok\n");
+
+  // 3. Large allocation
+  void *pLarge = malloc(1024);
+  uptr sLarge = __lf_get_size((uptr)pLarge);
+  if (((uptr)pLarge % sLarge) == 0 && sLarge == 1024)
+    printf("  1024: ok\n");
+
+  printf("OOM Fallback (Simulated):\n");
+  // Requesting a size larger than LowFat supports (e.g. > 1GB in default mode)
+  // should fall back to standard malloc.
+  size_t huge = 2ULL * 1024 * 1024 * 1024; // 2GB
+  void *pHuge = malloc(huge);
+  if (pHuge) {
+    uptr sHuge = __lf_get_size((uptr)pHuge);
+    // Should be non-LowFat (size == -1)
+    if (sHuge == (uptr)-1)
+      printf("  huge_fallback: ok\n");
+    free(pHuge);
+  } else {
+    // If system OOM'd, we can't test fallback, but we'll assume ok for now.
+    printf("  huge_fallback: ok (system oom)\n");
+  }
+
+  free(p16);
+  free(p17);
+  free(pLarge);
+
+  // CHECK: Heap Allocation (Rounding & Alignment):
+  // CHECK:   16: ok
+  // CHECK:   17: ok
+  // CHECK:   1024: ok
+  // CHECK: OOM Fallback (Simulated):
+  // CHECK:   huge_fallback: ok
+
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/TestCases/security_core_gep.cpp b/compiler-rt/test/lowfat/TestCases/security_core_gep.cpp
new file mode 100644
index 0000000000000..127f451eef829
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/security_core_gep.cpp
@@ -0,0 +1,31 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ESCAPE
+// RUN: %clangxx_lowfat -O0 %s -DSENTINEL -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-SENTINEL
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+// Use noinline to prevent the compiler from optimizing away the GEP.
+__attribute__((noinline)) void sink(void *p) {
+  printf("p = %p\n", p);
+}
+
+int main() {
+  char *p = (char *)malloc(16);
+  if (!p) return 1;
+
+#ifdef SENTINEL
+  // Test the "base - 1" sentinel idiom.
+  // CHECK-SENTINEL: LOWFAT ERROR: out-of-bounds error detected!
+  char *sentinel = p - 1;
+  sink(sentinel);
+#else
+  // Test pointer escaping an allocation boundary.
+  // CHECK-ESCAPE: LOWFAT ERROR: out-of-bounds error detected!
+  char *escaped = p + 16;
+  sink(escaped);
+#endif
+
+  free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/TestCases/security_core_oob.cpp b/compiler-rt/test/lowfat/TestCases/security_core_oob.cpp
new file mode 100644
index 0000000000000..7495e713614d1
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/security_core_oob.cpp
@@ -0,0 +1,32 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-READ
+// RUN: %clangxx_lowfat -O0 %s -DWRITE -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-WRITE
+// RUN: %clangxx_lowfat -O0 %s -DUNDERFLOW -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-UNDERFLOW
+
+#include <stdlib.h>
+#include <stdio.h>
+
+int main() {
+  char *p = (char *)malloc(16);
+  if (!p) return 1;
+
+#ifdef UNDERFLOW
+  // CHECK-UNDERFLOW: LOWFAT ERROR: out-of-bounds error detected!
+  // CHECK-UNDERFLOW: operation = read
+  char c = p[-1];
+  (void)c;
+#elif defined(WRITE)
+  // CHECK-WRITE: LOWFAT ERROR: out-of-bounds error detected!
+  // Note: GEP-level instrumentation fires before the store, and it currently
+  // always reports 'read' (0).
+  // CHECK-WRITE: operation = read
+  p[16] = 'x';
+#else
+  // CHECK-READ: LOWFAT ERROR: out-of-bounds error detected!
+  // CHECK-READ: operation = read
+  char c = p[16];
+  (void)c;
+#endif
+
+  free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/TestCases/security_core_padding.cpp b/compiler-rt/test/lowfat/TestCases/security_core_padding.cpp
new file mode 100644
index 0000000000000..3acf925f76316
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/security_core_padding.cpp
@@ -0,0 +1,28 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+int main() {
+  // Requesting 17 bytes will result in a 32-byte LowFat allocation.
+  char *p = (char *)malloc(17);
+  if (!p) return 1;
+
+  // Access within requested bounds.
+  p[0] = 'a';
+  p[16] = 'b';
+
+  // Access past requested bounds (17), but within allocation padding (32).
+  // LowFat enforces allocation-level bounds, so this should pass.
+  p[17] = 'c';
+  p[31] = 'z';
+
+  printf("Padding access: ok\n");
+  // CHECK: Padding access: ok
+  // CHECK-NOT: LOWFAT ERROR
+
+  free(p);
+  return 0;
+}

>From 656835605049c886ed8d9955a03f8820455adf49 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 11 Mar 2026 17:50:59 +0800
Subject: [PATCH 46/55] [LowFat] Omit mul path for pow2 mode

---
 .../Instrumentation/LowFatSanitizer.cpp       | 114 ++++++++++--------
 1 file changed, 61 insertions(+), 53 deletions(-)

diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 169af093fa18f..6050a0341161e 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -262,8 +262,6 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
   };
 
   Value *AllocSize64 = loadFromTable(getSizesTable(),  I64Ty, RegionIndex);
-  Value *Magic64     = loadFromTable(getMagicsTable(), I64Ty, RegionIndex);
-  Value *IsPow2_8    = loadFromTable(getIsPow2Table(), I8Ty,  RegionIndex);
   Value *Mask64      = loadFromTable(getMasksTable(),  I64Ty, RegionIndex);
 
   // Narrow to IntptrTy (which is i64 on 64-bit targets)
@@ -273,7 +271,31 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
   // --- AND (POW2) base ---
   Value *BaseAnd = IRB.CreateAnd(PtrInt, Mask);
 
+  // Build-time specialization: if we know the region is POW2 (or all are),
+  // skip the MUL path entirely to avoid the cmov.
+  bool KnownPow2 = false;
+  if (auto *CI = dyn_cast<ConstantInt>(RegionIndex)) {
+    uint64_t Idx = CI->getZExtValue();
+    if (Idx < LOWFAT_NUM_SIZE_CLASSES && kLowFatGenIsPow2[Idx])
+      KnownPow2 = true;
+  } else {
+    // Check if ALL configured regions are POW2.
+    KnownPow2 = true;
+    for (int i = 0; i < LOWFAT_NUM_SIZE_CLASSES; ++i) {
+      if (!kLowFatGenIsPow2[i]) {
+        KnownPow2 = false;
+        break;
+      }
+    }
+  }
+
+  if (KnownPow2)
+    return {AllocSize, BaseAnd};
+
   // --- MUL (non-POW2) base ---
+  Value *Magic64     = loadFromTable(getMagicsTable(), I64Ty, RegionIndex);
+  Value *IsPow2_8    = loadFromTable(getIsPow2Table(), I8Ty,  RegionIndex);
+
   Value *Ptr128   = IRB.CreateZExt(PtrInt, I128Ty);
   Value *Magic128 = IRB.CreateZExt(IRB.CreateZExtOrTrunc(Magic64, IntptrTy),
                                    I128Ty);
@@ -290,6 +312,38 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
 }
 #endif  // LOWFAT_CUSTOM_CONFIG
 
+// Emit the OOB-check block given a pre-computed (Base, AllocSize, PtrInt).
+void LowFatSanitizer::emitOobCheck(IRBuilder<> &IRB, Value *PtrInt, Value *Base,
+                                  Value *AllocSize, uint64_t FixedAccessSize,
+                                  Value *DynAccessSize,
+                                  Instruction *InsertBefore, bool IsWrite) {
+  Value *AccessSize = DynAccessSize;
+  if (!AccessSize)
+    AccessSize = ConstantInt::get(IntptrTy, FixedAccessSize);
+
+  Value *End = IRB.CreateAdd(Base, AllocSize);
+  Value *AccessEnd = IRB.CreateAdd(PtrInt, AccessSize);
+
+  // For GEPs (no AccessSize/DynAccessSize), we check if result is < Base OR >=
+  // End. For loads/stores, we just check if AccessEnd > End.
+  Value *IsOOB = nullptr;
+  if (!FixedAccessSize && !DynAccessSize) {
+    Value *TooLow = IRB.CreateICmpULT(PtrInt, Base);
+    Value *TooHigh = IRB.CreateICmpUGE(PtrInt, End);
+    IsOOB = IRB.CreateOr(TooLow, TooHigh);
+  } else {
+    IsOOB = IRB.CreateICmpUGT(AccessEnd, End);
+  }
+
+  Instruction *OobTerm =
+      SplitBlockAndInsertIfThen(IsOOB, InsertBefore, /*Unreachable=*/false);
+  IRBuilder<> OobIRB(OobTerm);
+  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
+  Type *I8Ty = Type::getInt8Ty(M.getContext());
+  Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
+  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
+}
+
 bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
                                               Type *AccessTy) {
   TypeSize AccessSize = DL.getTypeStoreSize(AccessTy);
@@ -316,25 +370,8 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
                  isa<AtomicCmpXchgInst>(I);
 
 #ifdef LOWFAT_CUSTOM_CONFIG
-  // --- Custom config: dual-path (AND vs. magic multiply) ---
-  // Emit dynamic table-lookup path; the static constant-folded path would
-  // require knowing the region index at IR-construction time, which is only
-  // possible for stack/global accesses. Heap accesses always go through here.
   auto [AllocSize, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
-
-  Value *AccessSizeVal = ConstantInt::get(IntptrTy, FixedAccessSize);
-  Value *AccessEnd     = ThenIRB.CreateAdd(PtrInt, AccessSizeVal);
-  Value *End           = ThenIRB.CreateAdd(Base, AllocSize);
-  Value *IsOOB         = ThenIRB.CreateICmpUGT(AccessEnd, End);
-
-  Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
-  IRBuilder<> OobIRB(OobTerm);
-  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
-  Type *I8Ty = Type::getInt8Ty(M.getContext());
-  Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
-  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
 #else
-  // --- POW2-only mode: existing shift-and-mask logic ---
   Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
   Value *ShiftAmount   = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
   Value *SizeOne       = ConstantInt::get(IntptrTy, 1);
@@ -342,20 +379,11 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   Value *SizeMinusOne  = ThenIRB.CreateSub(AllocSize, SizeOne);
   Value *Mask          = ThenIRB.CreateNot(SizeMinusOne);
   Value *Base          = ThenIRB.CreateAnd(PtrInt, Mask);
-  Value *End           = ThenIRB.CreateAdd(Base, AllocSize);
-
-  Value *AccessSizeVal = ConstantInt::get(IntptrTy, FixedAccessSize);
-  Value *AccessEnd     = ThenIRB.CreateAdd(PtrInt, AccessSizeVal);
-  Value *IsOOB         = ThenIRB.CreateICmpUGT(AccessEnd, End);
-
-  Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
-  IRBuilder<> OobIRB(OobTerm);
-  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
-  Type *I8Ty = Type::getInt8Ty(M.getContext());
-  Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
-  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
 #endif
 
+  emitOobCheck(ThenIRB, PtrInt, Base, AllocSize, FixedAccessSize, nullptr,
+               ThenTerm, IsWrite);
+
   if (isa<LoadInst>(I))          NumInstrumentedLoads++;
   else if (isa<StoreInst>(I))    NumInstrumentedStores++;
   else                           NumInstrumentedAtomics++;
@@ -391,16 +419,7 @@ bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
   Value *Base          = ThenIRB.CreateAnd(PtrInt, Mask);
 #endif
 
-  Value *End       = ThenIRB.CreateAdd(Base, AllocSize);
-  Value *AccessEnd = ThenIRB.CreateAdd(PtrInt, SizeInt);
-  Value *IsOOB     = ThenIRB.CreateICmpUGT(AccessEnd, End);
-
-  Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
-  IRBuilder<> OobIRB(OobTerm);
-  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
-  Type *I8Ty = Type::getInt8Ty(M.getContext());
-  Value *IsWriteVal = ConstantInt::get(I8Ty, IsWrite ? 1 : 0);
-  OobIRB.CreateCall(OobFn, {PtrInt, Base, AllocSize, IsWriteVal});
+  emitOobCheck(ThenIRB, PtrInt, Base, AllocSize, 0, SizeInt, ThenTerm, IsWrite);
 
   NumInstrumentedMemIntrinsics++;
   return true;
@@ -464,18 +483,7 @@ bool LowFatSanitizer::instrumentGEP(GetElementPtrInst *GEP) {
 #endif
 
   // 3. OOB if result underflows (< Base) or reaches/passes the end (>= Base+AllocSize).
-  Value *End     = ThenIRB.CreateAdd(Base, AllocSize);
-  Value *TooLow  = ThenIRB.CreateICmpULT(ResInt, Base);
-  Value *TooHigh = ThenIRB.CreateICmpUGE(ResInt, End);
-  Value *IsOOB   = ThenIRB.CreateOr(TooLow, TooHigh);
-
-  Instruction *OobTerm = SplitBlockAndInsertIfThen(IsOOB, ThenTerm, false);
-  IRBuilder<> OobIRB(OobTerm);
-  FunctionCallee OobFn = Options.Recover ? getWarnOobFn() : getReportOobFn();
-  Type *I8Ty = Type::getInt8Ty(M.getContext());
-  // GEPs are neither reads nor writes; report as read (0).
-  OobIRB.CreateCall(OobFn,
-                    {ResInt, Base, AllocSize, ConstantInt::get(I8Ty, 0)});
+  emitOobCheck(ThenIRB, ResInt, Base, AllocSize, 0, nullptr, ThenTerm, false);
 
   NumInstrumentedGEPs++;
   return true;

>From eea63674c3ddf56e5b4bf7f46cdddd05e509a243 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 11 Mar 2026 20:49:55 +0800
Subject: [PATCH 47/55] [LowFat] Use magic numbers for all custom sizes

---
 compiler-rt/lib/lowfat/tools/lf_config_gen.c          | 11 ++++++-----
 .../Transforms/Instrumentation/LowFatSanitizer.cpp    |  7 +------
 2 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/compiler-rt/lib/lowfat/tools/lf_config_gen.c b/compiler-rt/lib/lowfat/tools/lf_config_gen.c
index e9cd3d6d6502e..036d53c60530d 100644
--- a/compiler-rt/lib/lowfat/tools/lf_config_gen.c
+++ b/compiler-rt/lib/lowfat/tools/lf_config_gen.c
@@ -205,15 +205,15 @@ int main(int argc, char *argv[]) {
 
     is_pow2_arr[i] = pow2;
 
+    uint64_t M    = compute_magic(S);
+    uint64_t err  = precision_error(S, M);
+    magics[i]     = M;
+
     if (pow2) {
-      magics[i]         = 0;                // unused — POW2 uses AND
       masks[i]          = ~(S - 1);
       effective_sizes[i] = S;              // no precision error for POW2
     } else {
-      uint64_t M    = compute_magic(S);
-      uint64_t err  = precision_error(S, M);
-      magics[i]     = M;
-      masks[i]      = 0;                   // not applicable for non-POW2
+      masks[i]          = 0;                   // not applicable for non-POW2
       // Shrink effective size by error so allocator never gives out the
       // bytes that the magic-number math would mis-identify.
       effective_sizes[i] = S - err;
@@ -223,6 +223,7 @@ int main(int argc, char *argv[]) {
                 " bytes; effective size = %" PRIu64 "\n", S, err, effective_sizes[i]);
       }
     }
+
   }
 
   // ---- Open output ----
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 6050a0341161e..5b00d97597c7f 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -294,7 +294,6 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
 
   // --- MUL (non-POW2) base ---
   Value *Magic64     = loadFromTable(getMagicsTable(), I64Ty, RegionIndex);
-  Value *IsPow2_8    = loadFromTable(getIsPow2Table(), I8Ty,  RegionIndex);
 
   Value *Ptr128   = IRB.CreateZExt(PtrInt, I128Ty);
   Value *Magic128 = IRB.CreateZExt(IRB.CreateZExtOrTrunc(Magic64, IntptrTy),
@@ -304,11 +303,7 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
   Value *Idx      = IRB.CreateTrunc(Idx128, IntptrTy);
   Value *BaseMul  = IRB.CreateMul(Idx, AllocSize);
 
-  // Select between the two paths
-  Value *IsPow2_1 = IRB.CreateTrunc(IsPow2_8, Type::getInt1Ty(Ctx));
-  Value *Base     = IRB.CreateSelect(IsPow2_1, BaseAnd, BaseMul);
-
-  return {AllocSize, Base};
+  return {AllocSize, BaseMul};
 }
 #endif  // LOWFAT_CUSTOM_CONFIG
 

>From 4bf6249e20807f87035ff6022416fe3e6cc9db3d Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 11 Mar 2026 21:38:22 +0800
Subject: [PATCH 48/55] [LowFat] Use absolute address for metadata and optimize
 region checking

---
 compiler-rt/lib/lowfat/lf_rtl.cpp             |  52 ++++++++
 .../Instrumentation/LowFatSanitizer.cpp       | 114 ++++++++++--------
 2 files changed, 117 insertions(+), 49 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 5780264a5b29d..a4910da794d26 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -65,6 +65,17 @@ static FreeBlock *free_lists[kMaxSizeClasses];
 // size classes, which is the common case in multi-threaded programs.
 static StaticSpinMutex region_locks[kMaxSizeClasses];
 
+// Fixed address where the metadata tables (sizes, magics, is_pow2, masks)
+// are mapped during initialization. This allows the LLVM pass to use
+// absolute addressing (imm[index*8]) instead of PC-relative loads.
+//
+//   0x200000000: Sizes (8 bytes per class)
+//   0x201000000: Magics (8 bytes per class)
+//   0x202000000: IsPow2 (1 byte per class)
+//   0x203000000: Masks (8 bytes per class)
+static constexpr uptr kTablesBase   = 0x200000000ULL;
+static constexpr uptr kTablesOffset = 0x1000000ULL;  // 16 MB between tables
+
 static void InitializeFlags() {
   SetCommonFlagsDefaults();
 
@@ -85,6 +96,45 @@ static void InitializeFlags() {
   InitializeCommonFlags();
 }
 
+static void InitTables() {
+  // Map 64 MB of address space at kTablesBase for the metadata tables.
+  // This is enough for 2^21 (2 million) size classes, which covers the entire
+  // 64 TB address space given 32 GB regions.
+  if (!MmapFixedNoReserve(kTablesBase, 64 * 1024 * 1024, "lowfat_tables"))
+    Die();
+
+  u64 *sizes  = (u64 *)(kTablesBase + 0 * kTablesOffset);
+  u64 *magics = (u64 *)(kTablesBase + 1 * kTablesOffset);
+  u8  *ispow2 = (u8  *)(kTablesBase + 2 * kTablesOffset);
+  u64 *masks  = (u64 *)(kTablesBase + 3 * kTablesOffset);
+
+  // Initialize all possible region indices (up to 1024 for now, which covers 32TB)
+  // with "poison" values: Size=0, Base=0. Any access to a non-LowFat pointer
+  // will then result in (Base=0, End=0), which always fails the OOB check:
+  //   Ptr < 0 || Ptr >= 0  => Always True.
+  for (uptr i = 0; i < 1024; i++) {
+    if (i < kNumSizeClasses) {
+#ifdef LOWFAT_CUSTOM_CONFIG
+      sizes[i]  = (u64)kLowFatGenSizes[i];
+      magics[i] = (u64)kLowFatGenMagics[i];
+      ispow2[i] = (u8) kLowFatGenIsPow2[i];
+      masks[i]  = (u64)kLowFatGenMasks[i];
+#else
+      u64 size = (u64)SizeClassToSize(i);
+      sizes[i]  = size;
+      magics[i] = 0;
+      ispow2[i] = 1;
+      masks[i]  = ~(size - 1);
+#endif
+    } else {
+      sizes[i]  = 0;
+      magics[i] = 0;
+      ispow2[i] = 0;
+      masks[i]  = 0;
+    }
+  }
+}
+
 static void InitRegionTable() {
   for (uptr i = 0; i < kNumSizeClasses; i++) {
     uptr size = SizeClassToSize(i);
@@ -236,6 +286,8 @@ void __lf_init() {
 
   __lowfat::InitializeFlags();
 
+  __lowfat::InitTables();
+
   __lowfat::InitRegionTable();
 
   if (!__lowfat::InitMemoryRegions())
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 5b00d97597c7f..8ecb8b0ced1ae 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -99,6 +99,18 @@ class LowFatSanitizer {
   GlobalVariable *MasksTableGV  = nullptr;
 #endif
 
+  // Helper: GEP + load from a fixed absolute base at runtime index.
+  Value *loadFromFixedTable(IRBuilder<> &IRB, uint64_t TableBase,
+                            Type *ElemTy, Value *Idx) {
+    LLVMContext &Ctx = M.getContext();
+    Type *I64Ty = Type::getInt64Ty(Ctx);
+    Value *BasePtr = IRB.CreateIntToPtr(ConstantInt::get(I64Ty, TableBase),
+                                        PointerType::getUnqual(Ctx));
+    Value *Idx64   = IRB.CreateZExtOrTrunc(Idx, I64Ty);
+    Value *GEP     = IRB.CreateInBoundsGEP(ElemTy, BasePtr, {Idx64});
+    return IRB.CreateLoad(ElemTy, GEP);
+  }
+
   // Constants (kept in sync with lf_config.h / lf_config_generated.h)
 #ifdef LOWFAT_CUSTOM_CONFIG
   static constexpr uint64_t RegionBase     = 0x100000000000ULL;
@@ -111,6 +123,10 @@ class LowFatSanitizer {
   static constexpr uint64_t NumSizeClasses = 27; // kMaxSizeLog(30) - kMinSizeLog(4) + 1
   static constexpr uint64_t MinSizeLog     = 4;
 #endif
+
+  // Fixed absolute addresses for metadata tables (must match lf_rtl.cpp)
+  static constexpr uint64_t kTablesBase   = 0x200000000ULL;
+  static constexpr uint64_t kTablesOffset = 0x1000000ULL;
 };
 
 FunctionCallee LowFatSanitizer::getReportOobFn() {
@@ -248,21 +264,11 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
   LLVMContext &Ctx = M.getContext();
   Type *I64Ty  = Type::getInt64Ty(Ctx);
   Type *I128Ty = Type::getInt128Ty(Ctx);
-  Type *I8Ty   = Type::getInt8Ty(Ctx);
-
-  // Helper: GEP + load from a GlobalVariable array at runtime index.
-  auto loadFromTable = [&](GlobalVariable *GV, Type *ElemTy,
-                            Value *Idx) -> Value * {
-    Value *Zero = ConstantInt::get(Type::getInt64Ty(Ctx), 0);
-    // Extend RegionIndex to i64 if it's a different width
-    Value *Idx64 = IRB.CreateZExtOrTrunc(Idx, I64Ty);
-    Value *GEP   = IRB.CreateInBoundsGEP(GV->getValueType(), GV,
-                                          {Zero, Idx64});
-    return IRB.CreateLoad(ElemTy, GEP);
-  };
 
-  Value *AllocSize64 = loadFromTable(getSizesTable(),  I64Ty, RegionIndex);
-  Value *Mask64      = loadFromTable(getMasksTable(),  I64Ty, RegionIndex);
+  Value *AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+                                          I64Ty, RegionIndex);
+  Value *Mask64      = loadFromFixedTable(IRB, kTablesBase + 3 * kTablesOffset,
+                                          I64Ty, RegionIndex);
 
   // Narrow to IntptrTy (which is i64 on 64-bit targets)
   Value *AllocSize = IRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
@@ -293,7 +299,8 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
     return {AllocSize, BaseAnd};
 
   // --- MUL (non-POW2) base ---
-  Value *Magic64     = loadFromTable(getMagicsTable(), I64Ty, RegionIndex);
+  Value *Magic64     = loadFromFixedTable(IRB, kTablesBase + 1 * kTablesOffset,
+                                          I64Ty, RegionIndex);
 
   Value *Ptr128   = IRB.CreateZExt(PtrInt, I128Ty);
   Value *Magic128 = IRB.CreateZExt(IRB.CreateZExtOrTrunc(Magic64, IntptrTy),
@@ -354,9 +361,15 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   Value *RegionOffset  = IRB.CreateSub(PtrInt, RegionBaseVal);
   Value *RegionIndex   = IRB.CreateLShr(RegionOffset, RegionSizeLog);
 
-  // 2. Check if LowFat pointer: RegionIndex < NumSizeClasses
-  Value *MaxRegion  = ConstantInt::get(IntptrTy, NumSizeClasses);
-  Value *IsLowFat   = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+  // 2. Optimized IsLowFat check:
+  // Instead of comparing RegionIndex < NumSizeClasses, we load the Size
+  // from the fixed table and check if it's non-zero. If it's zero, this
+  // is not a LowFat pointer and we skip the check.
+  LLVMContext &Ctx = M.getContext();
+  Type *I64Ty = Type::getInt64Ty(Ctx);
+  Value *AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+                                          I64Ty, RegionIndex);
+  Value *IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
 
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
   IRBuilder<> ThenIRB(ThenTerm);
@@ -364,16 +377,15 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   bool IsWrite = isa<StoreInst>(I) || isa<AtomicRMWInst>(I) ||
                  isa<AtomicCmpXchgInst>(I);
 
+  Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
+
 #ifdef LOWFAT_CUSTOM_CONFIG
-  auto [AllocSize, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
+  auto [_, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
 #else
-  Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
-  Value *ShiftAmount   = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
-  Value *SizeOne       = ConstantInt::get(IntptrTy, 1);
-  Value *AllocSize     = ThenIRB.CreateShl(SizeOne, ShiftAmount);
-  Value *SizeMinusOne  = ThenIRB.CreateSub(AllocSize, SizeOne);
-  Value *Mask          = ThenIRB.CreateNot(SizeMinusOne);
-  Value *Base          = ThenIRB.CreateAnd(PtrInt, Mask);
+  Value *Mask64      = loadFromFixedTable(ThenIRB, kTablesBase + 3 * kTablesOffset,
+                                          I64Ty, RegionIndex);
+  Value *Mask      = ThenIRB.CreateZExtOrTrunc(Mask64, IntptrTy);
+  Value *Base      = ThenIRB.CreateAnd(PtrInt, Mask);
 #endif
 
   emitOobCheck(ThenIRB, PtrInt, Base, AllocSize, FixedAccessSize, nullptr,
@@ -396,22 +408,24 @@ bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
   Value *RegionOffset  = IRB.CreateSub(PtrInt, RegionBaseVal);
   Value *RegionIndex   = IRB.CreateLShr(RegionOffset, RegionSizeLog);
 
-  Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
-  Value *IsLowFat  = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+  LLVMContext &Ctx = M.getContext();
+  Type *I64Ty = Type::getInt64Ty(Ctx);
+  Value *AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+                                          I64Ty, RegionIndex);
+  Value *IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
 
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
   IRBuilder<> ThenIRB(ThenTerm);
 
+  Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
+
 #ifdef LOWFAT_CUSTOM_CONFIG
-  auto [AllocSize, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
+  auto [_, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
 #else
-  Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
-  Value *ShiftAmount   = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
-  Value *SizeOne       = ConstantInt::get(IntptrTy, 1);
-  Value *AllocSize     = ThenIRB.CreateShl(SizeOne, ShiftAmount);
-  Value *SizeMinusOne  = ThenIRB.CreateSub(AllocSize, SizeOne);
-  Value *Mask          = ThenIRB.CreateNot(SizeMinusOne);
-  Value *Base          = ThenIRB.CreateAnd(PtrInt, Mask);
+  Value *Mask64      = loadFromFixedTable(ThenIRB, kTablesBase + 3 * kTablesOffset,
+                                          I64Ty, RegionIndex);
+  Value *Mask      = ThenIRB.CreateZExtOrTrunc(Mask64, IntptrTy);
+  Value *Base      = ThenIRB.CreateAnd(PtrInt, Mask);
 #endif
 
   emitOobCheck(ThenIRB, PtrInt, Base, AllocSize, 0, SizeInt, ThenTerm, IsWrite);
@@ -454,30 +468,32 @@ bool LowFatSanitizer::instrumentGEP(GetElementPtrInst *GEP) {
   // RESULT pointer — what we're checking stays within [Base, Base+AllocSize).
   Value *ResInt = IRB.CreatePtrToInt(GEP, IntptrTy);
 
-  // 1. Is the source a LowFat pointer?
+  // 1. Compute Base and AllocSize from the SOURCE pointer.
   Value *RegionBaseVal = ConstantInt::get(IntptrTy, RegionBase);
   Value *RegionOffset  = IRB.CreateSub(SrcInt, RegionBaseVal);
   Value *RegionIndex   = IRB.CreateLShr(RegionOffset, RegionSizeLog);
-  Value *MaxRegion     = ConstantInt::get(IntptrTy, NumSizeClasses);
-  Value *IsLowFat      = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+
+  LLVMContext &Ctx = M.getContext();
+  Type *I64Ty = Type::getInt64Ty(Ctx);
+  Value *AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+                                          I64Ty, RegionIndex);
+  Value *IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
 
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, InsertPt, false);
   IRBuilder<> ThenIRB(ThenTerm);
 
-  // 2. Compute Base and AllocSize from the SOURCE pointer.
+  Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
+
 #ifdef LOWFAT_CUSTOM_CONFIG
-  auto [AllocSize, Base] = emitDynamicBaseMagic(ThenIRB, SrcInt, RegionIndex);
+  auto [_, Base] = emitDynamicBaseMagic(ThenIRB, SrcInt, RegionIndex);
 #else
-  Value *MinSizeLogVal = ConstantInt::get(IntptrTy, MinSizeLog);
-  Value *ShiftAmount   = ThenIRB.CreateAdd(RegionIndex, MinSizeLogVal);
-  Value *SizeOne       = ConstantInt::get(IntptrTy, 1);
-  Value *AllocSize     = ThenIRB.CreateShl(SizeOne, ShiftAmount);
-  Value *SizeMinusOne  = ThenIRB.CreateSub(AllocSize, SizeOne);
-  Value *Mask          = ThenIRB.CreateNot(SizeMinusOne);
-  Value *Base          = ThenIRB.CreateAnd(SrcInt, Mask);
+  Value *Mask64      = loadFromFixedTable(ThenIRB, kTablesBase + 3 * kTablesOffset,
+                                          I64Ty, RegionIndex);
+  Value *Mask      = ThenIRB.CreateZExtOrTrunc(Mask64, IntptrTy);
+  Value *Base      = ThenIRB.CreateAnd(SrcInt, Mask);
 #endif
 
-  // 3. OOB if result underflows (< Base) or reaches/passes the end (>= Base+AllocSize).
+  // 2. OOB if result underflows (< Base) or reaches/passes the end (>= Base+AllocSize).
   emitOobCheck(ThenIRB, ResInt, Base, AllocSize, 0, nullptr, ThenTerm, false);
 
   NumInstrumentedGEPs++;

>From 907be35126cb885b7cdcd8c36911a87f9e9124cd Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 11 Mar 2026 22:17:27 +0800
Subject: [PATCH 49/55] [LowFat] Use compact bounds check for access

---
 .../Instrumentation/LowFatSanitizer.cpp       | 25 +++++++++----------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 8ecb8b0ced1ae..67f74de396c36 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -319,22 +319,21 @@ void LowFatSanitizer::emitOobCheck(IRBuilder<> &IRB, Value *PtrInt, Value *Base,
                                   Value *AllocSize, uint64_t FixedAccessSize,
                                   Value *DynAccessSize,
                                   Instruction *InsertBefore, bool IsWrite) {
-  Value *AccessSize = DynAccessSize;
-  if (!AccessSize)
-    AccessSize = ConstantInt::get(IntptrTy, FixedAccessSize);
-
-  Value *End = IRB.CreateAdd(Base, AllocSize);
-  Value *AccessEnd = IRB.CreateAdd(PtrInt, AccessSize);
-
-  // For GEPs (no AccessSize/DynAccessSize), we check if result is < Base OR >=
-  // End. For loads/stores, we just check if AccessEnd > End.
   Value *IsOOB = nullptr;
   if (!FixedAccessSize && !DynAccessSize) {
-    Value *TooLow = IRB.CreateICmpULT(PtrInt, Base);
-    Value *TooHigh = IRB.CreateICmpUGE(PtrInt, End);
-    IsOOB = IRB.CreateOr(TooLow, TooHigh);
+    // Compact GEP check: OOB iff (ptr - base) >= alloc_size (unsigned).
+    // This catches both underflow and overflow without separate compares.
+    Value *Diff = IRB.CreateSub(PtrInt, Base);
+    IsOOB = IRB.CreateICmpUGE(Diff, AllocSize);
   } else {
-    IsOOB = IRB.CreateICmpUGT(AccessEnd, End);
+    Value *AccessSize = DynAccessSize;
+    if (!AccessSize)
+      AccessSize = ConstantInt::get(IntptrTy, FixedAccessSize);
+    Value *Diff = IRB.CreateSub(PtrInt, Base);
+    Value *TooWide = IRB.CreateICmpUGT(AccessSize, AllocSize);
+    Value *Limit = IRB.CreateSub(AllocSize, AccessSize);
+    Value *PastEnd = IRB.CreateICmpUGT(Diff, Limit);
+    IsOOB = IRB.CreateOr(TooWide, PastEnd);
   }
 
   Instruction *OobTerm =

>From e532797803cdb3099b80641c1628c6bea9ece021 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Wed, 18 Mar 2026 23:06:40 +0800
Subject: [PATCH 50/55] [LowFat] Add original sizes configuration

---
 compiler-rt/lib/lowfat/tools/sizes_orig.cfg | 33 +++++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 compiler-rt/lib/lowfat/tools/sizes_orig.cfg

diff --git a/compiler-rt/lib/lowfat/tools/sizes_orig.cfg b/compiler-rt/lib/lowfat/tools/sizes_orig.cfg
new file mode 100644
index 0000000000000..a04506cef22f1
--- /dev/null
+++ b/compiler-rt/lib/lowfat/tools/sizes_orig.cfg
@@ -0,0 +1,33 @@
+# Original sizes (from research paper)
+16
+32
+64
+128
+256
+512
+1024
+2048
+4096
+8192
+16384
+32768
+65536
+131072
+262144
+524288
+1048576
+2097152
+4194304
+8388608
+16777216
+33554432
+67108864
+134217728
+268435456
+536870912
+1073741824
+2147483648
+4294967296
+8589934592
+17179869184
+34359738368

>From 0d2ad6c452b42c04b802f381f3990c4053a69ec3 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Thu, 19 Mar 2026 16:44:06 +0800
Subject: [PATCH 51/55] [LowFat] Implement right-align mode with transform

---
 clang/lib/CodeGen/BackendUtil.cpp             |  4 +-
 compiler-rt/lib/lowfat/lf_interceptors.cpp    | 18 +++--
 compiler-rt/lib/lowfat/lf_interface.h         |  7 ++
 compiler-rt/lib/lowfat/lf_rtl.cpp             | 70 ++++++++++++++-----
 .../Instrumentation/LowFatSanitizer.h         |  7 +-
 .../Instrumentation/LowFatSanitizer.cpp       | 23 ++++++
 6 files changed, 105 insertions(+), 24 deletions(-)

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 2aff1a03c6e5d..54ea2751b853a 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -115,7 +115,9 @@ static cl::opt<LowFatSanitizerOptions::LowFatMode> LowFatMode(
         clEnumValN(LowFatSanitizerOptions::LowFatMode::Fast, "fast",
                    "Instrument at OptimizerLastEP (least overhead)"),
         clEnumValN(LowFatSanitizerOptions::LowFatMode::Safe, "safe",
-                   "Barrier at PipelineStartEP + instrument at OptimizerLastEP")));
+                   "Barrier at PipelineStartEP + instrument at OptimizerLastEP"),
+        clEnumValN(LowFatSanitizerOptions::LowFatMode::RightAlign, "right-align",
+                   "Right-align allocations within class slots to catch right-side OOB")));
 
 // Experiment to mark cold functions as optsize/minsize/optnone.
 // TODO: remove once this is exposed as a proper driver flag.
diff --git a/compiler-rt/lib/lowfat/lf_interceptors.cpp b/compiler-rt/lib/lowfat/lf_interceptors.cpp
index b857ca845b3ff..0a05d8eb1abed 100644
--- a/compiler-rt/lib/lowfat/lf_interceptors.cpp
+++ b/compiler-rt/lib/lowfat/lf_interceptors.cpp
@@ -22,6 +22,7 @@ using namespace __sanitizer;
 namespace __lowfat {
 extern bool lowfat_inited;
 extern bool lowfat_recover;
+extern bool lowfat_right_align;
 } // namespace __lowfat
 
 // DlsymAlloc handles allocations that happen before our runtime is initialized
@@ -105,11 +106,20 @@ INTERCEPTOR(void *, realloc, void *ptr, uptr size) {
     // For system pointers, we don't know exact old size, copy 'size' bytes.
     uptr copy_size = size;
     if (old_is_lowfat) {
-      uptr old_size = __lowfat::GetSize((uptr)ptr);
-      if (old_size < copy_size)
-        copy_size = old_size;
+      uptr old_class_size = __lowfat::GetSize((uptr)ptr);
+      if (old_class_size < copy_size)
+        copy_size = old_class_size;
     }
-    internal_memcpy(new_ptr, ptr, copy_size);
+    // In right-align mode the returned pointer is offset within its slot:
+    //   ptr = slot_base + (class_size - requested_size)
+    // Copying 'copy_size' bytes from 'ptr' would read past the slot end.
+    // Instead copy from the slot base so we stay within the mapped region.
+    // The user data starts at ptr, but copying from the base is safe since
+    // the left padding is zeroed on allocation and belongs to the same slot.
+    const void *copy_src = __lowfat::lowfat_right_align && old_is_lowfat
+                               ? (const void *)__lowfat::GetBase((uptr)ptr)
+                               : ptr;
+    internal_memcpy(new_ptr, copy_src, copy_size);
     // Free old
     if (old_is_lowfat)
       __lowfat::Deallocate(ptr);
diff --git a/compiler-rt/lib/lowfat/lf_interface.h b/compiler-rt/lib/lowfat/lf_interface.h
index b6840c4ef3e13..62a1199bdd6fa 100644
--- a/compiler-rt/lib/lowfat/lf_interface.h
+++ b/compiler-rt/lib/lowfat/lf_interface.h
@@ -35,6 +35,13 @@ SANITIZER_INTERFACE_ATTRIBUTE void __lf_init();
 // -fsanitize-recover=lowfat to the runtime interceptors.
 SANITIZER_INTERFACE_ATTRIBUTE void __lf_set_recover(int recover);
 
+// Called from a compiler-generated module constructor when -lowfat-mode=right-align
+// is active. Instructs the allocator to right-align objects within their size-class
+// slot so the object's right edge coincides with the slot boundary, turning any
+// off-by-one overflow into a detectable OOB. The trade-off is a blind spot on the
+// left (underflow) side of (class_size - requested_size) bytes.
+SANITIZER_INTERFACE_ATTRIBUTE void __lf_set_right_align(int right_align);
+
 SANITIZER_INTERFACE_ATTRIBUTE void __lf_report_oob(uptr ptr, uptr base,
                                                     uptr bound, int is_write);
 
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index a4910da794d26..47db1f98d81dc 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -34,6 +34,13 @@ bool lowfat_inited = false;
 // interceptor-level OOB (memset/memcpy/memmove) warns-and-continues or aborts.
 bool lowfat_recover = false;
 
+// Set to true when -lowfat-mode=right-align is active. Instructs Allocate()
+// to right-align objects within their size-class slot so the object's right
+// edge coincides with the slot boundary. This makes off-by-one overflows
+// detectable (right OOB is caught) at the cost of a left-side blind spot of
+// (class_size - requested_size) bytes.
+bool lowfat_right_align = false;
+
 // Maximum number of size classes across both modes.
 // In POW2-only mode kNumSizeClasses=27; with a custom config it can be up to
 // LOWFAT_MAX_ARRAY_SIZE. We size the static arrays at compile time using
@@ -183,6 +190,15 @@ static bool InitMemoryRegions() {
 // Allocate from a LowFat region
 // First checks the free list, then falls back to bump allocation.
 // Thread-safe: protected by per-size-class spin mutex.
+//
+// In right-align mode, returns slot_base + (class_size - requested_size) so
+// the object's right edge coincides with the slot boundary. The bounds check
+// (ptr - GetBase(ptr)) < class_size is still correct: GetBase() recovers
+// slot_base via mask/magic since slot_base is always class-aligned, and any
+// access past slot_base+class_size fails the check.
+//
+// The free list always stores slot bases (not right-aligned pointers) so that
+// freed blocks can be reused with a different offset for a new request size.
 void *Allocate(uptr size) {
   if (size == 0)
     size = 1;
@@ -195,31 +211,43 @@ void *Allocate(uptr size) {
 
   SpinMutexLock lock(&region_locks[class_index]);
 
-  // 1. Try free list first
+  uptr slot_base;
+
+  // 1. Try free list first (stores slot bases)
   FreeBlock *block = free_lists[class_index];
   if (block) {
     free_lists[class_index] = block->next;
-    // Zero the memory (free list pointer was stored here)
+    slot_base = (uptr)block;
+    // Zero the entire slot (free list pointer was stored at slot_base)
     internal_memset(block, 0, alloc_size);
-    return (void *)block;
-  }
-
-  // 2. Fall back to bump allocation
-  uptr region_end = GetRegionStart(class_index) + kRegionSize;
-  uptr addr = region_next_alloc[class_index];
+  } else {
+    // 2. Fall back to bump allocation
+    uptr region_end = GetRegionStart(class_index) + kRegionSize;
+    uptr addr = region_next_alloc[class_index];
+
+    if (addr + alloc_size > region_end) {
+      // Region exhausted: fall back to standard libc-style allocation.
+      // The resulting pointer will not be a LowFat pointer (wide-bounds).
+      return (void *)InternalAlloc(size);
+    }
 
-  if (addr + alloc_size > region_end) {
-    // Region exhausted: fall back to standard libc-style allocation.
-    // The resulting pointer will not be a LowFat pointer (wide-bounds).
-    return (void *)InternalAlloc(size);
+    region_next_alloc[class_index] = addr + alloc_size;
+    slot_base = addr;
   }
 
-  region_next_alloc[class_index] = addr + alloc_size;
-  return (void *)addr;
+  // In right-align mode shift the returned pointer so the object's right
+  // edge sits at the slot boundary, making overflows immediately detectable.
+  if (lowfat_right_align)
+    return (void *)(slot_base + (alloc_size - size));
+  return (void *)slot_base;
 }
 
-// Free a LowFat allocation by pushing it onto the free list.
+// Free a LowFat allocation by pushing its slot base onto the free list.
 // Thread-safe: protected by per-size-class spin mutex.
+//
+// We always push the slot base (GetBase(ptr)) rather than ptr itself so that
+// freed slots can be reused with a different right-align offset for a new
+// request size, and so the free list is consistent regardless of mode.
 void Deallocate(void *ptr) {
   if (!ptr)
     return;
@@ -233,11 +261,14 @@ void Deallocate(void *ptr) {
   }
 
   uptr region = GetRegionIndex(addr);
+  // Recover the slot base: in right-align mode ptr is offset within the slot;
+  // in normal mode GetBase(addr) == addr since allocations are class-aligned.
+  uptr slot_base = GetBase(addr);
 
   SpinMutexLock lock(&region_locks[region]);
 
-  // Push to the head of the free list for this size class
-  FreeBlock *block = (FreeBlock *)ptr;
+  // Push slot base to the head of the free list for this size class
+  FreeBlock *block = (FreeBlock *)slot_base;
   block->next = free_lists[region];
   free_lists[region] = block;
 }
@@ -279,6 +310,11 @@ void __lf_set_recover(int recover) {
   __lowfat::lowfat_recover = (recover != 0);
 }
 
+SANITIZER_INTERFACE_ATTRIBUTE
+void __lf_set_right_align(int right_align) {
+  __lowfat::lowfat_right_align = (right_align != 0);
+}
+
 SANITIZER_INTERFACE_ATTRIBUTE
 void __lf_init() {
   if (__lowfat::lowfat_inited)
diff --git a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
index 2403aa9da2430..543077677527e 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/LowFatSanitizer.h
@@ -17,8 +17,11 @@ struct LowFatSanitizerOptions {
   bool Recover = false;
 
   enum class LowFatMode {
-    Fast, /// instrument at OptimizerLastEP
-    Safe, /// Barrier at PipelineStartEP + instrument at OptimizerLastEP
+    Fast,       /// instrument at OptimizerLastEP
+    Safe,       /// Barrier at PipelineStartEP + instrument at OptimizerLastEP
+    RightAlign, /// Fast instrumentation + right-align allocations within class
+                /// slots to improve detection of right-side (overflow) OOB at
+                /// the cost of a blind spot on the left (underflow) side.
   };
   LowFatMode Mode = LowFatMode::Fast;
 
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 67f74de396c36..edd6b32913f33 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -611,6 +611,29 @@ bool LowFatSanitizer::run() {
     Modified = true;
   }
 
+  // Emit a module constructor that calls __lf_set_right_align(1) so the
+  // runtime allocator right-aligns objects within their size-class slot.
+  // Right-aligning places the object's right edge at the slot boundary,
+  // making off-by-one overflows detectable at the cost of a left-side
+  // blind spot of (class_size - requested_size) bytes.
+  if (Options.Mode == LowFatSanitizerOptions::LowFatMode::RightAlign) {
+    LLVMContext &Ctx = M.getContext();
+    FunctionType *SetRightAlignTy =
+        FunctionType::get(Type::getVoidTy(Ctx), {Type::getInt32Ty(Ctx)}, false);
+    FunctionCallee SetRightAlignFn =
+        M.getOrInsertFunction("__lf_set_right_align", SetRightAlignTy);
+    Function *Ctor = Function::Create(
+        FunctionType::get(Type::getVoidTy(Ctx), false),
+        GlobalValue::InternalLinkage, "__lowfat_set_right_align_ctor", &M);
+    BasicBlock *BB = BasicBlock::Create(Ctx, "entry", Ctor);
+    IRBuilder<> CtorBuilder(BB);
+    CtorBuilder.CreateCall(SetRightAlignFn,
+                           {ConstantInt::get(Type::getInt32Ty(Ctx), 1)});
+    CtorBuilder.CreateRetVoid();
+    appendToGlobalCtors(M, Ctor, /*Priority=*/0);
+    Modified = true;
+  }
+
   return Modified;
 }
 

>From d7323631809a486c15f5c3f2d079381afa46faf7 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Thu, 19 Mar 2026 16:44:51 +0800
Subject: [PATCH 52/55] [LowFat] Add tests for right-alignn mode

---
 .../TestCases/right_align/free_list_reuse.cpp | 37 +++++++++++++++++++
 .../lowfat/TestCases/right_align/inbounds.cpp | 32 ++++++++++++++++
 .../right_align/left_padding_blind_spot.cpp   | 33 +++++++++++++++++
 .../TestCases/right_align/overflow_caught.cpp | 27 ++++++++++++++
 compiler-rt/test/lowfat/lit.cfg.py            |  5 +++
 5 files changed, 134 insertions(+)
 create mode 100644 compiler-rt/test/lowfat/TestCases/right_align/free_list_reuse.cpp
 create mode 100644 compiler-rt/test/lowfat/TestCases/right_align/inbounds.cpp
 create mode 100644 compiler-rt/test/lowfat/TestCases/right_align/left_padding_blind_spot.cpp
 create mode 100644 compiler-rt/test/lowfat/TestCases/right_align/overflow_caught.cpp

diff --git a/compiler-rt/test/lowfat/TestCases/right_align/free_list_reuse.cpp b/compiler-rt/test/lowfat/TestCases/right_align/free_list_reuse.cpp
new file mode 100644
index 0000000000000..b5c506da2fb9f
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/right_align/free_list_reuse.cpp
@@ -0,0 +1,37 @@
+// RUN: %clangxx_lowfat_right_align -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_right_align -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+// Free-list reuse correctness test for right-align mode.
+//
+// Both 17 and 25 bytes land in the 32-byte class. When the 17-byte slot is
+// freed, Deallocate must push the slot BASE (not the shifted pointer slot_base+15)
+// onto the free list. The subsequent 25-byte allocation then reuses the same
+// slot but with a different offset (slot_base+7), and all 25 bytes must be
+// accessible without OOB.
+
+#include <cstdio>
+#include <cstdlib>
+
+int main() {
+  // First allocation: 17 bytes → offset 15 within 32-byte slot.
+  char *a = (char *)malloc(17);
+  if (!a) return 1;
+  for (int i = 0; i < 17; i++) a[i] = (char)i;
+  free(a);
+
+  // Second allocation: 25 bytes → offset 7 within the same (reused) 32-byte slot.
+  char *b = (char *)malloc(25);
+  if (!b) return 1;
+
+  // Write and read back all 25 bytes — none must trigger OOB.
+  for (int i = 0; i < 25; i++) b[i] = (char)(i + 1);
+  for (int i = 0; i < 25; i++)
+    if (b[i] != (char)(i + 1)) return 2;
+
+  free(b);
+
+  // CHECK: free_list_reuse: ok
+  // CHECK-NOT: LOWFAT ERROR
+  printf("free_list_reuse: ok\n");
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/TestCases/right_align/inbounds.cpp b/compiler-rt/test/lowfat/TestCases/right_align/inbounds.cpp
new file mode 100644
index 0000000000000..8a8877957aa93
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/right_align/inbounds.cpp
@@ -0,0 +1,32 @@
+// RUN: %clangxx_lowfat_right_align -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+// RUN: %clangxx_lowfat_right_align -O2 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+// In right-align mode, all accesses within the requested allocation size must
+// not trigger OOB (no false positives).
+//
+// A 17-byte request lands in the 32-byte class. In right-align mode the object
+// is placed at slot_base+15, so its right edge coincides with the slot boundary
+// at slot_base+32. Bytes buf[0]..buf[16] are all valid.
+
+#include <cstdio>
+#include <cstdlib>
+
+int main() {
+  // 17 bytes → 32-byte class; object at slot_base+15 in right-align mode.
+  char *p = (char *)malloc(17);
+  if (!p) return 1;
+
+  // Write every byte of the requested allocation.
+  for (int i = 0; i < 17; i++)
+    p[i] = (char)i;
+
+  // Read back and verify.
+  for (int i = 0; i < 17; i++)
+    if (p[i] != (char)i) return 2;
+
+  // CHECK: inbounds: ok
+  // CHECK-NOT: LOWFAT ERROR
+  printf("inbounds: ok\n");
+  free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/TestCases/right_align/left_padding_blind_spot.cpp b/compiler-rt/test/lowfat/TestCases/right_align/left_padding_blind_spot.cpp
new file mode 100644
index 0000000000000..a3cfb650230cd
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/right_align/left_padding_blind_spot.cpp
@@ -0,0 +1,33 @@
+// RUN: %clangxx_lowfat_right_align -O0 %s -o %t && %run %t 2>&1 | FileCheck %s
+
+// Documents the known trade-off of right-align mode: underflows into the left
+// padding are not caught because the shifted pointer still falls within the
+// same 32-byte slot.
+//
+// 17-byte object in right-align mode: slot = [slot_base, slot_base+32)
+//   left padding:  [slot_base,    slot_base+15)  ← blind spot
+//   live object:   [slot_base+15, slot_base+32)  ← buf[0]..buf[16]
+//
+// buf[-1] = slot_base+14, which is inside the slot:
+//   GetBase(slot_base+14) = slot_base
+//   (slot_base+14 - slot_base) = 14 < 32  → NOT OOB
+
+#include <cstdio>
+#include <cstdlib>
+
+int main() {
+  // 17 bytes → 32-byte class; object at slot_base+15.
+  char *buf = (char *)malloc(17);
+  if (!buf) return 1;
+
+  // Write one byte into the left padding (blind spot).
+  // This is technically out-of-bounds for the 17-byte allocation, but right-align
+  // mode cannot detect it because the access stays within the 32-byte slot.
+  buf[-1] = 'X';
+
+  // CHECK: blind spot: not caught (left padding)
+  // CHECK-NOT: LOWFAT ERROR
+  printf("blind spot: not caught (left padding)\n");
+  free(buf);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/TestCases/right_align/overflow_caught.cpp b/compiler-rt/test/lowfat/TestCases/right_align/overflow_caught.cpp
new file mode 100644
index 0000000000000..4abfa72700a12
--- /dev/null
+++ b/compiler-rt/test/lowfat/TestCases/right_align/overflow_caught.cpp
@@ -0,0 +1,27 @@
+// RUN: %clangxx_lowfat -O0 %s -o %t && %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-MISS
+// RUN: %clangxx_lowfat_right_align -O0 %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-CATCH
+
+// Mode-difference test: one-past-end overflow on a non-POW2-sized allocation.
+//
+// Default (left-align): 17-byte object at slot_base; buf[17] falls in the
+// 15-byte right padding → access is within the 32-byte slot → NOT caught.
+//
+// Right-align: 17-byte object at slot_base+15; buf[17] = slot_base+32, which
+// is exactly the slot boundary → OOB → caught.
+
+#include <cstdio>
+#include <cstdlib>
+
+int main() {
+  // 17 bytes → 32-byte class.
+  char *buf = (char *)malloc(17);
+  if (!buf) return 1;
+
+  buf[17] = 'X'; // one-past-end write
+
+  // CHECK-MISS: overflow: not caught (in right padding)
+  // CHECK-CATCH: LOWFAT ERROR: out-of-bounds error detected!
+  printf("overflow: not caught (in right padding)\n");
+  free(buf);
+  return 0;
+}
diff --git a/compiler-rt/test/lowfat/lit.cfg.py b/compiler-rt/test/lowfat/lit.cfg.py
index 948a4f0620d29..29bbd5ce2d409 100644
--- a/compiler-rt/test/lowfat/lit.cfg.py
+++ b/compiler-rt/test/lowfat/lit.cfg.py
@@ -44,8 +44,13 @@ def build_invocation(flags):
 # safe mode (fast mode is the default)
 lowfat_safe = lowfat_base + ["-mllvm", "-lowfat-mode=safe"]
 
+# right-align mode: allocations placed at slot_base+(class_size-requested_size)
+# so the object's right edge coincides with the slot boundary.
+lowfat_right_align = lowfat_base + ["-mllvm", "-lowfat-mode=right-align"]
+
 config.substitutions.append(("%clangxx_lowfat ", build_invocation(lowfat_base)))
 config.substitutions.append(("%clangxx_lowfat_safe ", build_invocation(lowfat_safe)))
+config.substitutions.append(("%clangxx_lowfat_right_align ", build_invocation(lowfat_right_align)))
 
 # Recover mode versions
 config.substitutions.append(

>From 05bdf473c34a9fe4bf88c34c5703b61222460a37 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 24 Mar 2026 13:31:08 +0800
Subject: [PATCH 53/55] [LowFat] Guard metadata loads with region range check
 on Darwin

---
 .../Instrumentation/LowFatSanitizer.cpp       | 61 ++++++++++++++-----
 1 file changed, 47 insertions(+), 14 deletions(-)

diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index edd6b32913f33..938d84d5b5804 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -26,6 +26,7 @@
 #include "llvm/IR/Type.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/ModRef.h"
+#include "llvm/TargetParser/Triple.h"
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
 #include "llvm/Transforms/Utils/ModuleUtils.h"
 
@@ -52,7 +53,8 @@ class LowFatSanitizer {
 public:
   LowFatSanitizer(Module &M, const LowFatSanitizerOptions &Options)
       : M(M), Options(Options), DL(M.getDataLayout()),
-        IntptrTy(DL.getIntPtrType(M.getContext())) {}
+        IntptrTy(DL.getIntPtrType(M.getContext())),
+        UseDarwinMetadataGuard(Triple(M.getTargetTriple()).isOSDarwin()) {}
 
   bool run();
 
@@ -61,6 +63,7 @@ class LowFatSanitizer {
   const LowFatSanitizerOptions &Options;
   const DataLayout &DL;
   Type *IntptrTy;
+  const bool UseDarwinMetadataGuard;
 
   FunctionCallee ReportOobFn = nullptr;
   FunctionCallee WarnOobFn = nullptr;
@@ -360,15 +363,22 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   Value *RegionOffset  = IRB.CreateSub(PtrInt, RegionBaseVal);
   Value *RegionIndex   = IRB.CreateLShr(RegionOffset, RegionSizeLog);
 
-  // 2. Optimized IsLowFat check:
-  // Instead of comparing RegionIndex < NumSizeClasses, we load the Size
-  // from the fixed table and check if it's non-zero. If it's zero, this
-  // is not a LowFat pointer and we skip the check.
+  // 2. Darwin-first safety guard:
+  // On Darwin, prove the pointer is in a valid LowFat region before touching
+  // the fixed metadata tables. Other targets keep the current table-driven
+  // classification for now.
   LLVMContext &Ctx = M.getContext();
   Type *I64Ty = Type::getInt64Ty(Ctx);
-  Value *AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
-                                          I64Ty, RegionIndex);
-  Value *IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
+  Value *AllocSize64 = nullptr;
+  Value *IsLowFat = nullptr;
+  if (UseDarwinMetadataGuard) {
+    Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
+    IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+  } else {
+    AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+                                     I64Ty, RegionIndex);
+    IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
+  }
 
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
   IRBuilder<> ThenIRB(ThenTerm);
@@ -376,6 +386,9 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
   bool IsWrite = isa<StoreInst>(I) || isa<AtomicRMWInst>(I) ||
                  isa<AtomicCmpXchgInst>(I);
 
+  if (!AllocSize64)
+    AllocSize64 = loadFromFixedTable(ThenIRB, kTablesBase + 0 * kTablesOffset,
+                                     I64Ty, RegionIndex);
   Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
 
 #ifdef LOWFAT_CUSTOM_CONFIG
@@ -409,13 +422,23 @@ bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
 
   LLVMContext &Ctx = M.getContext();
   Type *I64Ty = Type::getInt64Ty(Ctx);
-  Value *AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
-                                          I64Ty, RegionIndex);
-  Value *IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
+  Value *AllocSize64 = nullptr;
+  Value *IsLowFat = nullptr;
+  if (UseDarwinMetadataGuard) {
+    Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
+    IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+  } else {
+    AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+                                     I64Ty, RegionIndex);
+    IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
+  }
 
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, I, false);
   IRBuilder<> ThenIRB(ThenTerm);
 
+  if (!AllocSize64)
+    AllocSize64 = loadFromFixedTable(ThenIRB, kTablesBase + 0 * kTablesOffset,
+                                     I64Ty, RegionIndex);
   Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
 
 #ifdef LOWFAT_CUSTOM_CONFIG
@@ -474,13 +497,23 @@ bool LowFatSanitizer::instrumentGEP(GetElementPtrInst *GEP) {
 
   LLVMContext &Ctx = M.getContext();
   Type *I64Ty = Type::getInt64Ty(Ctx);
-  Value *AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
-                                          I64Ty, RegionIndex);
-  Value *IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
+  Value *AllocSize64 = nullptr;
+  Value *IsLowFat = nullptr;
+  if (UseDarwinMetadataGuard) {
+    Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
+    IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
+  } else {
+    AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+                                     I64Ty, RegionIndex);
+    IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
+  }
 
   Instruction *ThenTerm = SplitBlockAndInsertIfThen(IsLowFat, InsertPt, false);
   IRBuilder<> ThenIRB(ThenTerm);
 
+  if (!AllocSize64)
+    AllocSize64 = loadFromFixedTable(ThenIRB, kTablesBase + 0 * kTablesOffset,
+                                     I64Ty, RegionIndex);
   Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
 
 #ifdef LOWFAT_CUSTOM_CONFIG

>From 52875f3465d4e43691d1e780db6ad9e881d5db0c Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 24 Mar 2026 14:02:43 +0800
Subject: [PATCH 54/55] [LowFat] Move metadata tables to a higher fixed address

---
 compiler-rt/lib/lowfat/lf_rtl.cpp             | 12 ++++----
 .../Instrumentation/LowFatSanitizer.cpp       | 30 ++++++++++---------
 2 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 47db1f98d81dc..af6bedcfa934d 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -76,11 +76,11 @@ static StaticSpinMutex region_locks[kMaxSizeClasses];
 // are mapped during initialization. This allows the LLVM pass to use
 // absolute addressing (imm[index*8]) instead of PC-relative loads.
 //
-//   0x200000000: Sizes (8 bytes per class)
-//   0x201000000: Magics (8 bytes per class)
-//   0x202000000: IsPow2 (1 byte per class)
-//   0x203000000: Masks (8 bytes per class)
-static constexpr uptr kTablesBase   = 0x200000000ULL;
+//   0x118000000000: Sizes (8 bytes per class)
+//   0x118001000000: Magics (8 bytes per class)
+//   0x118002000000: IsPow2 (1 byte per class)
+//   0x118003000000: Masks (8 bytes per class)
+static constexpr uptr kTablesBase   = 0x118000000000ULL;
 static constexpr uptr kTablesOffset = 0x1000000ULL;  // 16 MB between tables
 
 static void InitializeFlags() {
@@ -392,4 +392,4 @@ __attribute__((section(".preinit_array"), used)) static auto preinit =
 __attribute__((constructor)) static void lowfat_constructor() {
   __lf_init();
 }
-#endif
\ No newline at end of file
+#endif
diff --git a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
index 938d84d5b5804..f4d287dd2a970 100644
--- a/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/LowFatSanitizer.cpp
@@ -54,7 +54,8 @@ class LowFatSanitizer {
   LowFatSanitizer(Module &M, const LowFatSanitizerOptions &Options)
       : M(M), Options(Options), DL(M.getDataLayout()),
         IntptrTy(DL.getIntPtrType(M.getContext())),
-        UseDarwinMetadataGuard(Triple(M.getTargetTriple()).isOSDarwin()) {}
+        UseDarwinMetadataGuard(Triple(M.getTargetTriple()).isOSDarwin()),
+        TablesBase(kTablesBase) {}
 
   bool run();
 
@@ -64,6 +65,7 @@ class LowFatSanitizer {
   const DataLayout &DL;
   Type *IntptrTy;
   const bool UseDarwinMetadataGuard;
+  const uint64_t TablesBase;
 
   FunctionCallee ReportOobFn = nullptr;
   FunctionCallee WarnOobFn = nullptr;
@@ -128,7 +130,7 @@ class LowFatSanitizer {
 #endif
 
   // Fixed absolute addresses for metadata tables (must match lf_rtl.cpp)
-  static constexpr uint64_t kTablesBase   = 0x200000000ULL;
+  static constexpr uint64_t kTablesBase   = 0x118000000000ULL;
   static constexpr uint64_t kTablesOffset = 0x1000000ULL;
 };
 
@@ -268,9 +270,9 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
   Type *I64Ty  = Type::getInt64Ty(Ctx);
   Type *I128Ty = Type::getInt128Ty(Ctx);
 
-  Value *AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+  Value *AllocSize64 = loadFromFixedTable(IRB, TablesBase + 0 * kTablesOffset,
                                           I64Ty, RegionIndex);
-  Value *Mask64      = loadFromFixedTable(IRB, kTablesBase + 3 * kTablesOffset,
+  Value *Mask64      = loadFromFixedTable(IRB, TablesBase + 3 * kTablesOffset,
                                           I64Ty, RegionIndex);
 
   // Narrow to IntptrTy (which is i64 on 64-bit targets)
@@ -302,7 +304,7 @@ LowFatSanitizer::emitDynamicBaseMagic(IRBuilder<> &IRB, Value *PtrInt,
     return {AllocSize, BaseAnd};
 
   // --- MUL (non-POW2) base ---
-  Value *Magic64     = loadFromFixedTable(IRB, kTablesBase + 1 * kTablesOffset,
+  Value *Magic64     = loadFromFixedTable(IRB, TablesBase + 1 * kTablesOffset,
                                           I64Ty, RegionIndex);
 
   Value *Ptr128   = IRB.CreateZExt(PtrInt, I128Ty);
@@ -375,7 +377,7 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
     Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
     IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
   } else {
-    AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+    AllocSize64 = loadFromFixedTable(IRB, TablesBase + 0 * kTablesOffset,
                                      I64Ty, RegionIndex);
     IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
   }
@@ -387,14 +389,14 @@ bool LowFatSanitizer::instrumentMemoryAccess(Instruction *I, Value *Ptr,
                  isa<AtomicCmpXchgInst>(I);
 
   if (!AllocSize64)
-    AllocSize64 = loadFromFixedTable(ThenIRB, kTablesBase + 0 * kTablesOffset,
+    AllocSize64 = loadFromFixedTable(ThenIRB, TablesBase + 0 * kTablesOffset,
                                      I64Ty, RegionIndex);
   Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
 
 #ifdef LOWFAT_CUSTOM_CONFIG
   auto [_, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
 #else
-  Value *Mask64      = loadFromFixedTable(ThenIRB, kTablesBase + 3 * kTablesOffset,
+  Value *Mask64      = loadFromFixedTable(ThenIRB, TablesBase + 3 * kTablesOffset,
                                           I64Ty, RegionIndex);
   Value *Mask      = ThenIRB.CreateZExtOrTrunc(Mask64, IntptrTy);
   Value *Base      = ThenIRB.CreateAnd(PtrInt, Mask);
@@ -428,7 +430,7 @@ bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
     Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
     IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
   } else {
-    AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+    AllocSize64 = loadFromFixedTable(IRB, TablesBase + 0 * kTablesOffset,
                                      I64Ty, RegionIndex);
     IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
   }
@@ -437,14 +439,14 @@ bool LowFatSanitizer::instrumentMemoryRange(Instruction *I, Value *Ptr,
   IRBuilder<> ThenIRB(ThenTerm);
 
   if (!AllocSize64)
-    AllocSize64 = loadFromFixedTable(ThenIRB, kTablesBase + 0 * kTablesOffset,
+    AllocSize64 = loadFromFixedTable(ThenIRB, TablesBase + 0 * kTablesOffset,
                                      I64Ty, RegionIndex);
   Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
 
 #ifdef LOWFAT_CUSTOM_CONFIG
   auto [_, Base] = emitDynamicBaseMagic(ThenIRB, PtrInt, RegionIndex);
 #else
-  Value *Mask64      = loadFromFixedTable(ThenIRB, kTablesBase + 3 * kTablesOffset,
+  Value *Mask64      = loadFromFixedTable(ThenIRB, TablesBase + 3 * kTablesOffset,
                                           I64Ty, RegionIndex);
   Value *Mask      = ThenIRB.CreateZExtOrTrunc(Mask64, IntptrTy);
   Value *Base      = ThenIRB.CreateAnd(PtrInt, Mask);
@@ -503,7 +505,7 @@ bool LowFatSanitizer::instrumentGEP(GetElementPtrInst *GEP) {
     Value *MaxRegion = ConstantInt::get(IntptrTy, NumSizeClasses);
     IsLowFat = IRB.CreateICmpULT(RegionIndex, MaxRegion);
   } else {
-    AllocSize64 = loadFromFixedTable(IRB, kTablesBase + 0 * kTablesOffset,
+    AllocSize64 = loadFromFixedTable(IRB, TablesBase + 0 * kTablesOffset,
                                      I64Ty, RegionIndex);
     IsLowFat = IRB.CreateICmpNE(AllocSize64, ConstantInt::get(I64Ty, 0));
   }
@@ -512,14 +514,14 @@ bool LowFatSanitizer::instrumentGEP(GetElementPtrInst *GEP) {
   IRBuilder<> ThenIRB(ThenTerm);
 
   if (!AllocSize64)
-    AllocSize64 = loadFromFixedTable(ThenIRB, kTablesBase + 0 * kTablesOffset,
+    AllocSize64 = loadFromFixedTable(ThenIRB, TablesBase + 0 * kTablesOffset,
                                      I64Ty, RegionIndex);
   Value *AllocSize = ThenIRB.CreateZExtOrTrunc(AllocSize64, IntptrTy);
 
 #ifdef LOWFAT_CUSTOM_CONFIG
   auto [_, Base] = emitDynamicBaseMagic(ThenIRB, SrcInt, RegionIndex);
 #else
-  Value *Mask64      = loadFromFixedTable(ThenIRB, kTablesBase + 3 * kTablesOffset,
+  Value *Mask64      = loadFromFixedTable(ThenIRB, TablesBase + 3 * kTablesOffset,
                                           I64Ty, RegionIndex);
   Value *Mask      = ThenIRB.CreateZExtOrTrunc(Mask64, IntptrTy);
   Value *Base      = ThenIRB.CreateAnd(SrcInt, Mask);

>From 354ce9a44e941853fe129edb48d3f1635082e346 Mon Sep 17 00:00:00 2001
From: duckyfuz <108561447+duckyfuz at users.noreply.github.com>
Date: Tue, 24 Mar 2026 14:07:46 +0800
Subject: [PATCH 55/55] [LowFat] Add stack traces upon error

---
 compiler-rt/lib/lowfat/CMakeLists.txt |  2 ++
 compiler-rt/lib/lowfat/lf_rtl.cpp     | 15 +++++++++----
 compiler-rt/lib/lowfat/lf_stack.cpp   | 27 ++++++++++++++++++++++
 compiler-rt/lib/lowfat/lf_stack.h     | 32 +++++++++++++++++++++++++++
 4 files changed, 72 insertions(+), 4 deletions(-)
 create mode 100644 compiler-rt/lib/lowfat/lf_stack.cpp
 create mode 100644 compiler-rt/lib/lowfat/lf_stack.h

diff --git a/compiler-rt/lib/lowfat/CMakeLists.txt b/compiler-rt/lib/lowfat/CMakeLists.txt
index 07e4ffe85af06..e85139582d840 100644
--- a/compiler-rt/lib/lowfat/CMakeLists.txt
+++ b/compiler-rt/lib/lowfat/CMakeLists.txt
@@ -59,12 +59,14 @@ endif()
 set(LOWFAT_SOURCES
   lf_rtl.cpp
   lf_interceptors.cpp
+  lf_stack.cpp
 )
 
 set(LOWFAT_HEADERS
   lf_allocator.h
   lf_config.h
   lf_interface.h
+  lf_stack.h
 )
 
 set(LOWFAT_CFLAGS ${SANITIZER_COMMON_CFLAGS})
diff --git a/compiler-rt/lib/lowfat/lf_rtl.cpp b/compiler-rt/lib/lowfat/lf_rtl.cpp
index 47db1f98d81dc..feb51e25050a1 100644
--- a/compiler-rt/lib/lowfat/lf_rtl.cpp
+++ b/compiler-rt/lib/lowfat/lf_rtl.cpp
@@ -17,6 +17,7 @@
 #include "lf_allocator.h"
 #include "lf_config.h"
 #include "lf_interface.h"
+#include "lf_stack.h"
 #include "sanitizer_common/sanitizer_common.h"
 #include "sanitizer_common/sanitizer_allocator_internal.h"
 #include "sanitizer_common/sanitizer_flag_parser.h"
@@ -290,13 +291,17 @@ static void PrintOobHeader(const char *level, uptr ptr, uptr base, uptr bound,
   Printf("\n");
 }
 
-static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound, int is_write) {
+static void PrintErrorAndDie(uptr ptr, uptr base, uptr bound, int is_write,
+                             const StackTrace &stack) {
   PrintOobHeader("ERROR", ptr, base, bound, is_write);
+  stack.Print();
   Die();
 }
 
-static void PrintWarning(uptr ptr, uptr base, uptr bound, int is_write) {
+static void PrintWarning(uptr ptr, uptr base, uptr bound, int is_write,
+                         const StackTrace &stack) {
   PrintOobHeader("WARNING", ptr, base, bound, is_write);
+  stack.Print();
 }
 
 }  // namespace __lowfat
@@ -336,12 +341,14 @@ void __lf_init() {
 
 SANITIZER_INTERFACE_ATTRIBUTE
 void __lf_report_oob(uptr ptr, uptr base, uptr bound, int is_write) {
-  __lowfat::PrintErrorAndDie(ptr, base, bound, is_write);
+  GET_STACK_TRACE_FATAL_HERE;
+  __lowfat::PrintErrorAndDie(ptr, base, bound, is_write, stack);
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
 void __lf_warn_oob(uptr ptr, uptr base, uptr bound, int is_write) {
-  __lowfat::PrintWarning(ptr, base, bound, is_write);
+  GET_STACK_TRACE_FATAL_HERE;
+  __lowfat::PrintWarning(ptr, base, bound, is_write, stack);
 }
 
 SANITIZER_INTERFACE_ATTRIBUTE
diff --git a/compiler-rt/lib/lowfat/lf_stack.cpp b/compiler-rt/lib/lowfat/lf_stack.cpp
new file mode 100644
index 0000000000000..030ea4074af0e
--- /dev/null
+++ b/compiler-rt/lib/lowfat/lf_stack.cpp
@@ -0,0 +1,27 @@
+//===-- lf_stack.cpp - LowFat stack trace implementation ------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Provides BufferedStackTrace::UnwindImpl for the LowFat runtime.
+//
+// LowFat does not maintain a thread registry, so we obtain the stack bounds
+// directly from the OS via GetThreadStackTopAndBottom().
+//
+//===----------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+void __sanitizer::BufferedStackTrace::UnwindImpl(
+    uptr pc, uptr bp, void *context, bool request_fast, u32 max_depth) {
+  size = 0;
+  request_fast = StackTrace::WillUseFastUnwind(request_fast);
+  uptr stack_top = 0, stack_bottom = 0;
+  GetThreadStackTopAndBottom(/*at_initialization=*/false, &stack_top,
+                             &stack_bottom);
+  Unwind(max_depth, pc, bp, context, stack_top, stack_bottom, request_fast);
+}
diff --git a/compiler-rt/lib/lowfat/lf_stack.h b/compiler-rt/lib/lowfat/lf_stack.h
new file mode 100644
index 0000000000000..06aa3981e1629
--- /dev/null
+++ b/compiler-rt/lib/lowfat/lf_stack.h
@@ -0,0 +1,32 @@
+//===-- lf_stack.h - LowFat stack trace utilities ---------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Macros for capturing and printing stack traces in the LowFat runtime.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LF_STACK_H
+#define LF_STACK_H
+
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+// Capture a stack trace at the current call site, suitable for fatal error
+// reporting.  The resulting BufferedStackTrace is named `stack`.
+#define GET_STACK_TRACE_FATAL_HERE                                          \
+  UNINITIALIZED __sanitizer::BufferedStackTrace stack;                      \
+  stack.Unwind(__sanitizer::StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), \
+               nullptr, common_flags()->fast_unwind_on_fatal)
+
+// Capture and immediately print the stack trace at the current call site.
+#define PRINT_CURRENT_STACK() \
+  {                           \
+    GET_STACK_TRACE_FATAL_HERE; \
+    stack.Print();            \
+  }
+
+#endif  // LF_STACK_H



More information about the llvm-commits mailing list