[compiler-rt] [llvm] [TSan] Add escape analysis for redundant instrumentation elimination (PR #169896)

via llvm-commits llvm-commits at lists.llvm.org
Fri Nov 28 02:42:45 PST 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Alexey Paznikov (apaznikov)

<details>
<summary>Changes</summary>

## Summary
This PR implements a new static analysis pass, `EscapeAnalysis`, to identify and eliminate redundant memory access instrumentation in ThreadSanitizer. The pass determines whether a memory allocation (stack or heap) is confined to the current thread's execution scope. Accesses to such "non-escaping" objects cannot participate in data races and are therefore safe to exclude from instrumentation.

This work is part of a broader research effort on optimizing dynamic race detectors [1].

## Implementation Details
The core logic is implemented in `EscapeAnalysis.h/cpp` and integrated into `ThreadSanitizer.cpp`. The analysis operates intra-procedurally and heavily leverages **MemorySSA**:

1.  **MemorySSA-driven Dataflow:** The analysis uses MemorySSA to trace the lifecycle of pointers through memory operations. This allows it to:
    *   **Look through loads:** Identify the underlying allocation base object even if the pointer is loaded from stack slots or aggregates.
    *   **Track indirect escapes:** Detect scenarios where an address is stored into another object (e.g., a struct or array) and verify if that container object allows the pointer to leak (via a subsequent load or escape of the container itself).
2.  **Escape Criteria:** An object is considered escaping if:
    *   It is stored to a global variable or an escaping object.
    *   It is returned from the function.
    *   It is passed as an argument to a function call that captures the pointer (checked via `CaptureTracking`).
    *   It is involved in complex casts (e.g., `ptrtoint`) or volatile operations.
3.  **Heap Support:** Unlike simple stack-only analyses, this implementation identifies non-escaping heap allocations (e.g., a buffer allocated via `malloc` and used within the same function).

## Impact
*   **Runtime Performance:** Eliminating checks for thread-local data significantly reduces overhead, especially in functions with heavy usage of temporary buffers or local aggregates.
*   **Memory Overhead:** By not instrumenting local allocations, TSan avoids allocating shadow memory for them. This leads to a measurable reduction in memory consumption.

## Motivation & Potential Impact
This work is based on our research [1] into optimizing dynamic race detectors. Our experiments show that Escape Analysis (EA) is highly effective for specific workloads and complementary to other techniques.

**Runtime Speedup (EA Only):**
In our research prototype, isolating Escape Analysis yielded the following speedups:
*   **SQLite:** ~1.43x speedup (Significant gain due to heavy use of local structures).
*   **FFmpeg:** ~1.11x speedup.
*   **MySQL:** ~1.10x - 1.14x speedup.

**Memory Overhead Reduction:**
A key advantage of EA is reducing the memory footprint of the sanitizer by preventing shadow memory allocation for thread-local objects.
In our experiments with the **full optimization suite** (TSan+AllOpt) [1], we observed significant memory overhead reductions:
*   **FFmpeg:** ~10% reduction.
*   **Chromium:** ~7% reduction.
*   **Redis/SQLite:** ~4-6% reduction.
*As noted in the research, Escape Analysis is the **primary driver** of these memory savings.*

**Compilation Overhead:**
While the full research prototype (involving inter-procedural analyses) incurred a moderate build time overhead (~6-15%), this specific **intra-procedural** implementation is lightweight and is expected to have a **negligible impact** on compilation time.

### Note on this PR
This patch implements a **conservative, intra-procedural** version of the algorithm described in [1]. While the full research prototype uses Inter-Procedural Analysis (IPA) to track escapes across function boundaries, this upstream version is restricted to the function scope to ensure maximum stability, fast compilation, and compatibility with the existing LLVM pipeline. It's control-flow unsensitive as well. Despite this limitation, it effectively handles common patterns like local buffers and temporary objects passed to `nocapture` helpers.

## Usage
The optimization is currently opt-in.
**Flag:** `-mllvm -tsan-use-escape-analysis`

## Attribution & Status
**Implementation:**
This patch was implemented by **Alexey Paznikov**.

**Research & Algorithm Design:**
The underlying algorithms and performance validation were conducted by the research team: **Alexey Paznikov**, **Andrey Kogutenko**, **Yaroslav Osipov**, **Michael Schwarz**, and **Umang Mathur**.

This work is based on research currently **under review** for publication [1].

[1] "Optimizing Instrumentation for Data Race Detectors" (Under Review, 2025).

---

Patch is 93.78 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/169896.diff


11 Files Affected:

- (modified) compiler-rt/test/tsan/CMakeLists.txt (+81-30) 
- (modified) compiler-rt/test/tsan/lit.cfg.py (+7) 
- (modified) compiler-rt/test/tsan/lit.site.cfg.py.in (+3) 
- (added) llvm/include/llvm/Transforms/Instrumentation/EscapeAnalysis.h (+191) 
- (modified) llvm/lib/Passes/PassBuilder.cpp (+1) 
- (modified) llvm/lib/Passes/PassRegistry.def (+2) 
- (modified) llvm/lib/Transforms/Instrumentation/CMakeLists.txt (+1) 
- (added) llvm/lib/Transforms/Instrumentation/EscapeAnalysis.cpp (+742) 
- (modified) llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp (+87-20) 
- (added) llvm/test/Instrumentation/ThreadSanitizer/escape-analysis-tsan.ll (+250) 
- (added) llvm/test/Instrumentation/ThreadSanitizer/escape-analysis.ll (+843) 


``````````diff
diff --git a/compiler-rt/test/tsan/CMakeLists.txt b/compiler-rt/test/tsan/CMakeLists.txt
index 163355d68ebc2..828e6478d540c 100644
--- a/compiler-rt/test/tsan/CMakeLists.txt
+++ b/compiler-rt/test/tsan/CMakeLists.txt
@@ -18,6 +18,7 @@ endif()
 set(TSAN_DYNAMIC_TEST_DEPS ${TSAN_TEST_DEPS})
 set(TSAN_TESTSUITES)
 set(TSAN_DYNAMIC_TESTSUITES)
+set(TSAN_ENABLE_ESCAPE_ANALYSIS "False") # Disable escape analysis by default
 
 if (NOT DEFINED TSAN_TEST_DEFLAKE_THRESHOLD)
   set(TSAN_TEST_DEFLAKE_THRESHOLD "10")
@@ -28,45 +29,77 @@ if(APPLE)
   darwin_filter_host_archs(TSAN_SUPPORTED_ARCH TSAN_TEST_ARCH)
 endif()
 
-foreach(arch ${TSAN_TEST_ARCH})
-  set(TSAN_TEST_APPLE_PLATFORM "osx")
-  set(TSAN_TEST_MIN_DEPLOYMENT_TARGET_FLAG "${DARWIN_osx_MIN_VER_FLAG}")
+# Unified function for generating TSAN test suites by architectures.
+# Arguments:
+#   OUT_LIST_VAR    - name of output list (for example, TSAN_TESTSUITES or TSAN_EA_TESTSUITES)
+#   SUFFIX_KIND     - string added to config suffix after "-${arch}" (for example, "" or "-escape")
+#   CONFIG_KIND     - string added to config name after "Config" (for example, "" or "Escape")
+#   ENABLE_EA       - "True"/"False" enable escape analysis
+function(tsan_generate_arch_suites OUT_LIST_VAR SUFFIX_KIND CONFIG_KIND ENABLE_EA)
+  foreach(arch ${TSAN_TEST_ARCH})
+    set(TSAN_ENABLE_ESCAPE_ANALYSIS "${ENABLE_EA}")
 
-  set(TSAN_TEST_TARGET_ARCH ${arch})
-  string(TOLOWER "-${arch}" TSAN_TEST_CONFIG_SUFFIX)
-  get_test_cc_for_arch(${arch} TSAN_TEST_TARGET_CC TSAN_TEST_TARGET_CFLAGS)
+    set(TSAN_TEST_APPLE_PLATFORM "osx")
+    set(TSAN_TEST_MIN_DEPLOYMENT_TARGET_FLAG "${DARWIN_osx_MIN_VER_FLAG}")
 
-  string(REPLACE ";" " " LIBDISPATCH_CFLAGS_STRING " ${COMPILER_RT_TEST_LIBDISPATCH_CFLAGS}")
-  string(APPEND TSAN_TEST_TARGET_CFLAGS ${LIBDISPATCH_CFLAGS_STRING})
+    set(TSAN_TEST_TARGET_ARCH ${arch})
+    string(TOLOWER "-${arch}${SUFFIX_KIND}" TSAN_TEST_CONFIG_SUFFIX)
+    get_test_cc_for_arch(${arch} TSAN_TEST_TARGET_CC TSAN_TEST_TARGET_CFLAGS)
 
-  if (COMPILER_RT_HAS_MSSE4_2_FLAG)
-    string(APPEND TSAN_TEST_TARGET_CFLAGS " -msse4.2 ")
-  endif()
+    string(REPLACE ";" " " LIBDISPATCH_CFLAGS_STRING " ${COMPILER_RT_TEST_LIBDISPATCH_CFLAGS}")
+    string(APPEND TSAN_TEST_TARGET_CFLAGS ${LIBDISPATCH_CFLAGS_STRING})
 
-  string(TOUPPER ${arch} ARCH_UPPER_CASE)
-  set(CONFIG_NAME ${ARCH_UPPER_CASE}Config)
+    if (COMPILER_RT_HAS_MSSE4_2_FLAG)
+      string(APPEND TSAN_TEST_TARGET_CFLAGS " -msse4.2 ")
+    endif()
 
-  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 TSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+    string(TOUPPER ${arch} ARCH_UPPER_CASE)
+    set(CONFIG_NAME ${ARCH_UPPER_CASE}Config${CONFIG_KIND})
 
-  if(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME)
-    string(TOLOWER "-${arch}-${OS_NAME}-dynamic" TSAN_TEST_CONFIG_SUFFIX)
-    set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}DynamicConfig)
     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
+            ${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 ${OUT_LIST_VAR} ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+
+    if(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME)
+      # Dynamic runtime for corresponding variant
+      if("${SUFFIX_KIND}" STREQUAL "")
+        string(TOLOWER "-${arch}-${OS_NAME}-dynamic" TSAN_TEST_CONFIG_SUFFIX)
+        set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}DynamicConfig${CONFIG_KIND})
+        list(APPEND TSAN_DYNAMIC_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+      else()
+        string(TOLOWER "-${arch}-${OS_NAME}-dynamic${SUFFIX_KIND}" TSAN_TEST_CONFIG_SUFFIX)
+        set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}DynamicConfig${CONFIG_KIND})
+        # Track dynamic escape-analysis suites separately for a dedicated target.
+        list(APPEND TSAN_EA_DYNAMIC_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+      endif()
+      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 TSAN_DYNAMIC_TESTSUITES
-      ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+      list(APPEND ${OUT_LIST_VAR} ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+    endif()
+  endforeach()
+
+  # Propagate the assembled list to the parent scope
+  set(${OUT_LIST_VAR} "${${OUT_LIST_VAR}}" PARENT_SCOPE)
+  if(DEFINED TSAN_EA_DYNAMIC_TESTSUITES)
+    set(TSAN_EA_DYNAMIC_TESTSUITES "${TSAN_EA_DYNAMIC_TESTSUITES}" PARENT_SCOPE)
   endif()
-endforeach()
+endfunction()
+
+# Default configuration
+set(TSAN_TESTSUITES)
+tsan_generate_arch_suites(TSAN_TESTSUITES "" "" "False")
+
+# Enable escape analysis (check-tsan-escape-analysis target)
+set(TSAN_EA_TESTSUITES)
+tsan_generate_arch_suites(TSAN_EA_TESTSUITES "-escape" "Escape" "True")
 
 # iOS and iOS simulator test suites
 # These are not added into "check-all", in order to run these tests, use
@@ -124,6 +157,10 @@ list(APPEND TSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit)
 if(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME)
   list(APPEND TSAN_DYNAMIC_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit/dynamic)
 endif()
+list(APPEND TSAN_EA_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit)
+if(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME)
+  list(APPEND TSAN_EA_DYNAMIC_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit/dynamic)
+endif()
 
 add_lit_testsuite(check-tsan "Running ThreadSanitizer tests"
   ${TSAN_TESTSUITES}
@@ -136,3 +173,17 @@ if(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME)
                     EXCLUDE_FROM_CHECK_ALL
                     DEPENDS ${TSAN_DYNAMIC_TEST_DEPS})
 endif()
+
+add_lit_testsuite(check-tsan-escape-analysis "Running ThreadSanitizer tests (escape analysis)"
+        ${TSAN_EA_TESTSUITES}
+        DEPENDS ${TSAN_TEST_DEPS})
+set_target_properties(check-tsan-escape-analysis PROPERTIES FOLDER "Compiler-RT Tests")
+
+# New target: dynamic + escape analysis
+if(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME)
+  add_lit_testsuite(check-tsan-escape-analysis-dynamic "Running ThreadSanitizer tests (dynamic, escape analysis)"
+                    ${TSAN_EA_DYNAMIC_TESTSUITES}
+                    EXCLUDE_FROM_CHECK_ALL
+                    DEPENDS ${TSAN_DYNAMIC_TEST_DEPS})
+  set_target_properties(check-tsan-escape-analysis-dynamic PROPERTIES FOLDER "Compiler-RT Tests")
+endif()
\ No newline at end of file
diff --git a/compiler-rt/test/tsan/lit.cfg.py b/compiler-rt/test/tsan/lit.cfg.py
index 8803a7bda9aa5..829ee625499aa 100644
--- a/compiler-rt/test/tsan/lit.cfg.py
+++ b/compiler-rt/test/tsan/lit.cfg.py
@@ -56,6 +56,13 @@ def get_required_attr(config, attr_name):
     + extra_cflags
     + ["-I%s" % tsan_incdir]
 )
+# Setup escape analysis if enabled
+tsan_enable_escape = getattr(config, "tsan_enable_escape_analysis", "False") == "True"
+if tsan_enable_escape:
+    config.name += " (escape-analysis)"
+    ea_flags = [ "-mllvm", "-tsan-use-escape-analysis" ]
+    clang_tsan_cflags += ea_flags
+
 clang_tsan_cxxflags = (
     config.cxx_mode_flags + clang_tsan_cflags + ["-std=c++11"] + ["-I%s" % tsan_incdir]
 )
diff --git a/compiler-rt/test/tsan/lit.site.cfg.py.in b/compiler-rt/test/tsan/lit.site.cfg.py.in
index c6d453aaee26f..da38c9b7eb9b9 100644
--- a/compiler-rt/test/tsan/lit.site.cfg.py.in
+++ b/compiler-rt/test/tsan/lit.site.cfg.py.in
@@ -9,6 +9,9 @@ config.target_cflags = "@TSAN_TEST_TARGET_CFLAGS@"
 config.target_arch = "@TSAN_TEST_TARGET_ARCH@"
 config.deflake_threshold = "@TSAN_TEST_DEFLAKE_THRESHOLD@"
 
+# Enable escape analysis.
+config.tsan_enable_escape_analysis = "@TSAN_ENABLE_ESCAPE_ANALYSIS@"
+
 # 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/llvm/include/llvm/Transforms/Instrumentation/EscapeAnalysis.h b/llvm/include/llvm/Transforms/Instrumentation/EscapeAnalysis.h
new file mode 100644
index 0000000000000..a12b3ff374cda
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Instrumentation/EscapeAnalysis.h
@@ -0,0 +1,191 @@
+//===- EscapeAnalysis.h - Intraprocedural Escape Analysis -------*- 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 defines the interface for a simple, conservative intraprocedural
+// escape analysis. It is designed as a helper utility for other passes, like
+// ThreadSanitizer, to determine if an allocation escapes the context of its
+// containing function.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_INSTRUMENTATION_ESCAPEANALYSIS_H
+#define LLVM_TRANSFORMS_INSTRUMENTATION_ESCAPEANALYSIS_H
+
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Analysis/CaptureTracking.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/MemorySSA.h"
+#include "llvm/Analysis/TargetLibraryInfo.h"
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+/// Find underlying base objects for a pointer possibly produced by loads.
+///
+/// This routine walks backwards through MemorySSA clobbering definitions of
+/// simple loads to find stores that defined the loaded pointer values, and
+/// collects their base objects. Additionally, it attempts ValueTracking
+/// `getUnderlyingObjects` to peel pointer casts/GEPs/phis where profitable.
+///
+/// Collected "base" objects are:
+///  - `AllocaInst` (stack, base=stack)
+///  - `Argument` (function argument, base=arg)
+///  - `GlobalVariable` and `GlobalAlias` (base=global|alias)
+///  - `ConstantPointerNull` (base=null)
+///  - Results of known heap-allocating calls (e.g. `malloc`, `calloc`,
+///    `realloc`, `aligned_alloc`, `strdup`, or C\+\+ `new`) when recognized
+///    via `TargetLibraryInfo` (base=heap).
+///
+/// If the walk encounters an unrecognized defining write, a non-simple store,
+/// a memintrinsic as a defining write, or the step budget is exceeded, the
+/// analysis conservatively treats the current value as a terminal non-base
+/// and marks the result as incomplete.
+///
+/// Contract and guarantees:
+///  - If `MSSA` is null, the analysis immediately returns with
+///    `*IsComplete == false` (if provided).
+///  - If `TLI` is null, heap allocations cannot be recognized; terminals that
+///    are calls are treated as non-bases and lead to `*IsComplete == false`.
+///  - `Result` is a set of terminal values observed (may include non-bases if
+///    the analysis is incomplete). Use `*IsComplete` to know if all are bases.
+///  - `MaxSteps` is a per-query safety valve limiting the combined number of
+///    processed worklist nodes. When exceeded, the analysis bails out and
+///    sets `*IsComplete == false`.
+void getUnderlyingObjectsThroughLoads(const Value *Ptr, MemorySSA *MSSA,
+                                      SmallPtrSetImpl<const Value *> &Result,
+                                      const TargetLibraryInfo *TLI = nullptr,
+                                      LoopInfo *LI = nullptr,
+                                      bool *IsComplete = nullptr,
+                                      unsigned MaxSteps = 10000);
+
+/// Detect heap allocations. Complements isAllocationFn() by checking
+/// library functions directly when attributes might be missing.
+bool isHeapAllocation(const CallBase *CB, const TargetLibraryInfo &TLI);
+
+/// EscapeAnalysisInfo - This class implements the actual backward dataflow
+/// analysis for a function; queries are per allocation site.
+///
+/// This is a lightweight, intraprocedural and conservative analysis intended
+/// to help instrumentation passes (e.g. ThreadSanitizer) skip objects that do
+/// not escape the function scope. The main query is \c isEscaping(Value&),
+/// which answers whether an allocation site (alloca/malloc-like) may escape
+/// the current function. Results are memoized per underlying object.
+struct EscapeAnalysisInfo {
+  /// Constructs an escape analysis utility for a given function.
+  /// Requires a FunctionAnalysisManager to obtain other analyses like AA.
+  EscapeAnalysisInfo(Function &F, FunctionAnalysisManager &FAM) : F(F) {
+    TLI = &FAM.getResult<TargetLibraryAnalysis>(F);
+    MSSA = &FAM.getResult<MemorySSAAnalysis>(F).getMSSA();
+    LI = &FAM.getResult<LoopAnalysis>(F);
+  };
+  ~EscapeAnalysisInfo() = default;
+
+  /// Return true if \p Alloc may escape the function.
+  /// \param Alloc - Must be an allocation site (AllocaInst or heap allocation
+  ///                call). Passing GEPs/bitcasts is not supported; use the base
+  ///                allocation.
+  /// \returns true if the allocation escapes or if \p Alloc is not an
+  /// allocation site.
+  bool isEscaping(const Value &Alloc);
+
+  /// Print escape information for all allocations in the function
+  void print(raw_ostream &OS);
+
+  bool invalidate(Function &F, const PreservedAnalyses &PA,
+                  FunctionAnalysisManager::Invalidator &Inv);
+
+private:
+  Function &F;
+  DenseMap<const Value *, bool> Cache;
+
+  TargetLibraryInfo *TLI = nullptr;
+  MemorySSA *MSSA = nullptr;
+  LoopInfo *LI = nullptr;
+
+  /// Checks whether a base location is externally visible (thus escapes).
+  static bool isExternalObject(const Value *Base);
+
+  /// Custom CaptureTracker for escape analysis
+  class EscapeCaptureTracker : public CaptureTracker {
+  public:
+    EscapeCaptureTracker(EscapeAnalysisInfo &EAI,
+                         const SmallPtrSet<const Value *, 32> &ProcessingSet)
+        : EAI(EAI), ProcessingSet(ProcessingSet) {}
+
+    void tooManyUses() override { Escaped = true; }
+    bool shouldExplore(const Use *U) override;
+    Action captured(const Use *U, UseCaptureInfo CI) override;
+    bool hasEscaped() const { return Escaped; }
+
+  private:
+    EscapeAnalysisInfo &EAI;
+    SmallPtrSet<const Value *, 32> ProcessingSet;
+    bool Escaped = false;
+
+    /// Analyze if storing to destination causes escape
+    bool doesStoreDestEscape(const Value *Dest);
+
+    /// Get indices of pointer-typed arguments that are marked 'nocapture'
+    SmallVector<unsigned, 8>
+    getNoCapturePointerArgIndices(const CallBase *CB) const;
+
+    /// Check if any of the 'nocapture' arguments can reach the query object
+    bool canEscapeViaNocaptureArgs(
+        const CallBase &CB, ArrayRef<unsigned> NoCapPtrArgs,
+        SmallPtrSetImpl<const Value *> &StorePtrOpndBases) const;
+
+    /// Check if the given clobber stems from StartMDef
+    bool stemsFromStartStore(MemoryUseOrDef *MUOD, const MemoryDef *StartMDef,
+                             MemoryLocation Loc, bool &IsComplete,
+                             MemorySSAWalker *Walker) const;
+
+    /// Walk MemorySSA forward from StartStore and:
+    ///  - collect pointer-typed Loads that may read bytes written by StartStore
+    ///  - detect calls that may export those bytes via nocapture pointer args
+    /// Sets ContentMayEscape if any call may export the bytes.
+    SmallVector<const LoadInst *, 32>
+    findStoreReadersAndExports(const StoreInst *StartStore,
+                                 bool &ContentMayEscape, bool &IsComplete);
+
+    /// Analyze whether the pointer value stored by `Store` can escape
+    bool doesStoredPointerEscapeViaLoads(const StoreInst *Store);
+  };
+
+  /// Solve escape for a single allocation site using backward dataflow.
+  bool solveEscapeFor(const Value &Ptr,
+                      SmallPtrSet<const Value *, 32> &ProcessingSet);
+
+  /// Helper function to detect allocation sites (malloc/new-like)
+  /// Returns true if V is an Alloca or a call to a known heap alloc function.
+  bool isAllocationSite(const Value *V);
+};
+
+/// EscapeAnalysisInfo wrapper for the new pass manager.
+class EscapeAnalysis : public AnalysisInfoMixin<EscapeAnalysis> {
+  friend AnalysisInfoMixin<EscapeAnalysis>;
+  static AnalysisKey Key;
+
+public:
+  using Result = EscapeAnalysisInfo;
+  static Result run(Function &F, FunctionAnalysisManager &FAM);
+};
+
+/// Printer pass for the \c EscapeAnalysis results.
+class EscapeAnalysisPrinterPass
+    : public PassInfoMixin<EscapeAnalysisPrinterPass> {
+  raw_ostream &OS;
+
+public:
+  explicit EscapeAnalysisPrinterPass(raw_ostream &OS) : OS(OS) {}
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) const;
+  static bool isRequired() { return true; }
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_INSTRUMENTATION_ESCAPEANALYSIS_H
\ No newline at end of file
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index f5281ea69b512..4007b46acfdb5 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -245,6 +245,7 @@
 #include "llvm/Transforms/Instrumentation/CGProfile.h"
 #include "llvm/Transforms/Instrumentation/ControlHeightReduction.h"
 #include "llvm/Transforms/Instrumentation/DataFlowSanitizer.h"
+#include "llvm/Transforms/Instrumentation/EscapeAnalysis.h"
 #include "llvm/Transforms/Instrumentation/GCOVProfiler.h"
 #include "llvm/Transforms/Instrumentation/HWAddressSanitizer.h"
 #include "llvm/Transforms/Instrumentation/InstrProfiling.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 074c328ef0931..c72655d75bcfb 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -356,6 +356,7 @@ FUNCTION_ANALYSIS("demanded-bits", DemandedBitsAnalysis())
 FUNCTION_ANALYSIS("domfrontier", DominanceFrontierAnalysis())
 FUNCTION_ANALYSIS("domtree", DominatorTreeAnalysis())
 FUNCTION_ANALYSIS("ephemerals", EphemeralValuesAnalysis())
+FUNCTION_ANALYSIS("escape-analysis", EscapeAnalysis())
 FUNCTION_ANALYSIS("func-properties", FunctionPropertiesAnalysis())
 FUNCTION_ANALYSIS("machine-function-info", MachineFunctionAnalysis(*TM))
 FUNCTION_ANALYSIS("gc-function", GCFunctionAnalysis())
@@ -512,6 +513,7 @@ FUNCTION_PASS("print<delinearization>", DelinearizationPrinterPass(errs()))
 FUNCTION_PASS("print<demanded-bits>", DemandedBitsPrinterPass(errs()))
 FUNCTION_PASS("print<domfrontier>", DominanceFrontierPrinterPass(errs()))
 FUNCTION_PASS("print<domtree>", DominatorTreePrinterPass(errs()))
+FUNCTION_PASS("print<escape-analysis>", EscapeAnalysisPrinterPass(errs()))
 FUNCTION_PASS("print<func-properties>", FunctionPropertiesPrinterPass(errs()))
 FUNCTION_PASS("print<inline-cost>", InlineCostAnnotationPrinterPass(errs()))
 FUNCTION_PASS("print<lazy-value-info>", LazyValueInfoPrinterPass(errs()))
diff --git a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
index 80576c61fd80c..4369dfddbd439 100644
--- a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
+++ b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
@@ -24,6 +24,7 @@ add_llvm_component_library(LLVMInstrumentation
   SanitizerBinaryMetadata.cpp
   ValueProfileCollector.cpp
   ThreadSanitizer.cpp
+  EscapeAnalysis.cpp
   TypeSanitizer.cpp
   HWAddressSanitizer.cpp
   RealtimeSanitizer.cpp
diff --git a/llvm/lib/Transforms/Instrumentation/EscapeAnalysis.cpp b/llvm/lib/Transforms/Instrumentation/EscapeAnalysis.cpp
new file mode 100644
index 0000000000000..c72ca2431a9ee
--- /dev/null
+++ b/llvm/lib/Transforms/Instrumentation/EscapeAnalysis.cpp
@@ -0,0 +1,742 @@
+//===- EscapeAnalysis.cpp - Intraprocedural Escape Analysis 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the EscapeAnalysis helper class. It uses a worklist-
+// based, backward dataflow analysis to determine if an allocation can...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/169896


More information about the llvm-commits mailing list