Add benchmarking support for pointer nullability analysis.

This change includes somewhat arbitrary functions as benchmarks. Most are synthetic, with two derived from a real example. We need to grow these into a more principled sets of benchmark functions.

PiperOrigin-RevId: 579872437
Change-Id: Iffc02c0dc2805015d11d847ebc9eb422eea2139e
diff --git a/nullability/BUILD b/nullability/BUILD
index 316833b..5c62ecf 100644
--- a/nullability/BUILD
+++ b/nullability/BUILD
@@ -186,3 +186,21 @@
         "@llvm-project//third-party/unittest:gmock",
     ],
 )
+
+cc_test(
+    name = "pointer_nullability_analysis_benchmark",
+    srcs = ["pointer_nullability_analysis_benchmark.cc"],
+    tags = ["benchmark"],
+    deps = [
+        ":pointer_nullability_analysis",
+        ":pointer_nullability_diagnosis",
+        "@absl//absl/log:check",
+        "@absl//absl/strings",
+        "@absl//absl/strings:string_view",
+        "@com_google_googletest//:gtest_main",
+        "@llvm-project//clang:analysis",
+        "@llvm-project//clang:ast",
+        "@llvm-project//clang:basic",
+        "@llvm-project//clang:testing",
+    ],
+)
diff --git a/nullability/pointer_nullability_analysis_benchmark.cc b/nullability/pointer_nullability_analysis_benchmark.cc
new file mode 100644
index 0000000..1a7ba06
--- /dev/null
+++ b/nullability/pointer_nullability_analysis_benchmark.cc
@@ -0,0 +1,223 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <cstdint>
+
+#include "testing/base/public/benchmark.h"
+#include "absl/log/check.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "nullability/pointer_nullability_analysis.h"
+#include "nullability/pointer_nullability_diagnosis.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Testing/TestAST.h"
+
+namespace clang::tidy::nullability {
+namespace {
+
+NamedDecl *lookup(absl::string_view Name, const DeclContext &DC) {
+  auto Result = DC.lookup(&DC.getParentASTContext().Idents.get(Name));
+  CHECK(Result.isSingleResult()) << Name;
+  return Result.front();
+}
+
+void benchmarkAnalysisOnCode(benchmark::State &State, llvm::StringRef Code) {
+  TestAST AST(Code);
+  auto *Target = cast<FunctionDecl>(
+      lookup("Target", *AST.context().getTranslationUnitDecl()));
+
+  auto Diagnoser = pointerNullabilityDiagnoser();
+  constexpr std::int64_t MaxSATIterations = 1'000'000;
+  for (auto _ : State) {
+    (void)dataflow::diagnoseFunction<PointerNullabilityAnalysis,
+                                     PointerNullabilityDiagnostic>(
+        *Target, AST.context(), Diagnoser, MaxSATIterations);
+  }
+}
+
+void BM_PointerAnalysisCopyPointer(benchmark::State &State) {
+  benchmarkAnalysisOnCode(State, R"cpp(
+    int *Target(int *p) {
+      int *q = p;
+      return q;
+    }
+  )cpp");
+}
+BENCHMARK(BM_PointerAnalysisCopyPointer);
+
+void BM_PointerAnalysisIntLoop(benchmark::State &State) {
+  benchmarkAnalysisOnCode(State, R"cpp(
+    int Target(int *p) {
+      for (int i = 0; i < 10; ++i) *p += i;
+      return *p;
+    }
+  )cpp");
+}
+BENCHMARK(BM_PointerAnalysisIntLoop);
+
+void BM_PointerAnalysisPointerLoop(benchmark::State &State) {
+  benchmarkAnalysisOnCode(State, R"cpp(
+    int *_Nullable next();
+    void Target(int i) {
+      for (int *p = next(); p != nullptr; p = next()) *p += i;
+    }
+  )cpp");
+}
+BENCHMARK(BM_PointerAnalysisPointerLoop);
+
+void BM_PointerAnalysisBranch(benchmark::State &State) {
+  benchmarkAnalysisOnCode(State, R"cpp(
+    int Target(int *p, bool b) {
+      int i = 0;
+      if (b)
+        i = *p;
+      else
+        p = nullptr;
+      return *p;
+    }
+  )cpp");
+}
+BENCHMARK(BM_PointerAnalysisBranch);
+
+void BM_PointerAnalysisLoopAndBranch(benchmark::State &State) {
+  benchmarkAnalysisOnCode(State, R"cpp(
+    int *_Nullable next();
+    bool cond();
+    void Target(int *p, bool b) {
+      int x = 0;
+      for (int *p = next(); p != nullptr; p = next()) {
+        if (cond())
+          x = *p;  // arbitrary code with `*p`.
+        else
+          *p = x;  // different code with `*p`.
+      }
+    }
+  )cpp");
+}
+BENCHMARK(BM_PointerAnalysisLoopAndBranch);
+
+void BM_PointerAnalysisTwoLoops(benchmark::State &State) {
+  benchmarkAnalysisOnCode(State, R"cpp(
+    int Target(int *p, bool b) {
+      int x = 0;
+      for (int i = 0; i < 10; ++i) {
+        x += *p;
+      }
+      x = 7;
+      for (int i = 0; i < 10; ++i) {
+        x += *p;
+      }
+      return *p;
+    }
+  )cpp");
+}
+BENCHMARK(BM_PointerAnalysisTwoLoops);
+
+constexpr inline char preamble[] = R"cpp(
+  namespace std {
+  using size_t = unsigned;
+
+  template <typename T>
+  class vector {
+   public:
+    using iterator = T*;
+    size_t size() const;
+    iterator begin();
+    iterator end();
+  };
+
+  class string_view {
+   public:
+    bool empty();
+    char front();
+    char* data();
+    size_t size() const;
+    void remove_prefix(size_t);
+  };
+
+  class string {
+   public:
+    struct iterator {
+      char& operator*();
+      iterator& operator++();
+      iterator operator++(int);
+      iterator& operator+=(unsigned);
+      friend size_t operator-(const iterator&, const iterator&);
+      friend bool operator!=(const iterator&, const iterator&);
+    };
+    void resize(size_t);
+    void erase(size_t);
+    iterator begin();
+    iterator end();
+  };
+  }  // namespace std
+)cpp";
+
+// This benchmark is a simplified version of a function that joins two file-path
+// strings.
+void BM_PointerAnalysisJoinFilePath(benchmark::State &State) {
+  absl::string_view code = R"cpp(
+    std::string Target(std::vector<std::string_view> paths) {
+      std::string result;
+
+      if (paths.size() == 0) return result;
+
+      std::size_t total_size = paths.size() - 1;
+      for (const std::string_view path : paths) {
+        total_size += path.size();
+      }
+      result.resize(total_size);
+
+      auto begin = result.begin();
+      auto out = begin;
+      for (std::string_view path : paths) {
+        if (path.empty()) continue;
+        if (path.front() != '/' && out != begin) {
+          *out++ = '/';
+        }
+        const std::size_t this_size = path.size();
+        out += this_size;
+      }
+      result.erase(out - begin);
+
+      return result;
+    }
+  )cpp";
+  benchmarkAnalysisOnCode(State, absl::StrCat(preamble, code));
+}
+BENCHMARK(BM_PointerAnalysisJoinFilePath);
+
+// In practice, the call to `memcpy` inside the loop demonstrated a substantial
+// impact on microbenchmark performance. It is unclear why, and probably worth
+// further reducing this benchmark. For now, it seems interesting enough to
+// include in the suite.
+void BM_PointerAnalysisCallInLoop(benchmark::State &State) {
+  absl::string_view code = R"cpp(
+    void* memcpy(void* dest, const void* src, std::size_t count);
+
+    void Target(char* out, std::vector<std::string_view> paths) {
+      if (paths.size() != 0) {
+        std::size_t total_size = paths.size() - 1;
+        for (const std::string_view path : paths) {
+          total_size += path.size();
+        }
+        for (std::string_view path : paths) {
+          if (path.empty()) continue;
+          const std::size_t this_size = path.size();
+          memcpy(out, path.data(), this_size);
+          out += this_size;
+        }
+      }
+    }
+  )cpp";
+  benchmarkAnalysisOnCode(State, absl::StrCat(preamble, code));
+}
+BENCHMARK(BM_PointerAnalysisCallInLoop);
+
+}  // namespace
+}  // namespace clang::tidy::nullability