[llvm] [Analysis] Add function-scoped DDG DOT printer (dot-function-ddg) (PR #209089)

Sumukh J Bharadwaj via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 13 01:38:49 PDT 2026


https://github.com/amd-subharad updated https://github.com/llvm/llvm-project/pull/209089

>From bafd5dab916a0eac0e2bc74643f2fbccb4e5be58 Mon Sep 17 00:00:00 2001
From: Sumukh Bharadwaj <Sumukh.Bharadwaj at amd.com>
Date: Wed, 17 Jun 2026 17:31:58 +0530
Subject: [PATCH] [Analysis] Add function-scoped DDG DOT printer
 (dot-function-ddg)

Add FunctionDDGDotPrinterPass, the function-scoped counterpart to the
loop-scoped DDGDotPrinterPass (dot-ddg). The loop-scoped printer only
covers code recognised as a loop nest; this pass builds a
DataDependenceGraph for a whole function -- via the existing
DataDependenceGraph(Function&, DependenceInfo&) constructor -- and renders
it with the shared DDG DOTGraphTraits, so straight-line and cross-loop
code can be visualised with output identical in form to dot-ddg.

The pass is opt-only, registered as the FUNCTION_PASS "dot-function-ddg",
and writes one DOT file per function named "<prefix>.<function-name>.dot":

  - -dot-function-ddg-filename-prefix=<prefix> (default "ddg");
  - -dot-function-ddg-only selects the simplified rendering (the
    function-scope analogue of -dot-ddg-only).

It is strictly additive: DDG.{h,cpp}, DDGPrinter.{h,cpp}, GraphWriter.h,
and DOTGraphTraits.h are unchanged. Documentation is added in
llvm/docs/FunctionDDG.md (wired into the llvm/docs/UserGuides.md toctree)
with a summary entry in llvm/docs/Passes.md, and tested by
llvm/test/Analysis/DDG/print-dot-function-ddg.ll (register def-use and
memory-dependence edges, in both the default and -dot-function-ddg-only
forms).
---
 llvm/docs/FunctionDDG.md                      | 65 +++++++++++++++++++
 llvm/docs/Passes.md                           | 12 ++++
 llvm/docs/UserGuides.md                       |  6 ++
 .../llvm/Analysis/FunctionDDGPrinter.h        | 46 +++++++++++++
 llvm/lib/Analysis/CMakeLists.txt              |  1 +
 llvm/lib/Analysis/FunctionDDGPrinter.cpp      | 58 +++++++++++++++++
 llvm/lib/Passes/PassBuilder.cpp               |  1 +
 llvm/lib/Passes/PassRegistry.def              |  1 +
 .../Analysis/DDG/print-dot-function-ddg.ll    | 48 ++++++++++++++
 9 files changed, 238 insertions(+)
 create mode 100644 llvm/docs/FunctionDDG.md
 create mode 100644 llvm/include/llvm/Analysis/FunctionDDGPrinter.h
 create mode 100644 llvm/lib/Analysis/FunctionDDGPrinter.cpp
 create mode 100644 llvm/test/Analysis/DDG/print-dot-function-ddg.ll

diff --git a/llvm/docs/FunctionDDG.md b/llvm/docs/FunctionDDG.md
new file mode 100644
index 0000000000000..b9a1b646215ff
--- /dev/null
+++ b/llvm/docs/FunctionDDG.md
@@ -0,0 +1,65 @@
+# Function-scoped DDG visualizer
+
+`FunctionDDGDotPrinterPass` (`llvm/lib/Analysis/FunctionDDGPrinter.cpp`) is the
+`dot-function-ddg` pass: a function-scoped DOT printer for the Data Dependence
+Graph. It is the function-scope counterpart to the loop-scoped `DDGDotPrinterPass`
+(`dot-ddg`), and is intended for **human inspection** of a function's dependence
+structure.
+
+---
+
+## 1. Why a function-scoped DDG
+
+The loop-scoped DDG printer (`dot-ddg`) only covers code that has been recognised
+as a loop nest. To visualise the dependence structure of straight-line,
+cross-loop, or fully-unrolled code, a function-wide view is needed. This pass
+builds a `DataDependenceGraph` for the whole function (via the existing
+`DataDependenceGraph(Function&, DependenceInfo&)` constructor) and renders it
+with the shared DDG `DOTGraphTraits`, so loop- and function-scope graphs look
+identical. No DDG infrastructure changes are required; the pass is purely
+additive.
+
+## 2. Running it
+
+```bash
+opt -disable-output -passes=dot-function-ddg foo.ll
+```
+
+This writes one DOT file per function, named `<prefix>.<function-name>.dot`
+(default prefix `ddg`). Render with Graphviz:
+
+```bash
+dot -Tpng ddg.my_function.dot -o my_function.png
+```
+
+## 3. Flags
+
+| Flag | Effect |
+|------|--------|
+| `-dot-function-ddg-filename-prefix=<prefix>` | Output filename prefix; file is `<prefix>.<function-name>.dot` (default `ddg`). |
+| `-dot-function-ddg-only` | Simplified rendering (the function-scope analogue of `-dot-ddg-only`): concise node labels and edge attributes. |
+
+## 4. What the graph shows
+
+The graph is emitted by the shared DDG `DOTGraphTraits`
+(`llvm/lib/Analysis/DDGPrinter.cpp`), identical to the loop-scoped `dot-ddg`
+output but built over the whole function:
+
+- one node per DDG node (each carrying its instruction text);
+- **register def-use** edges (blue) and **memory-dependence** edges (red),
+  labelled with the edge kind (or, in verbose mode, the detailed dependence);
+- pi-blocks and the synthetic root node handled exactly as in the loop printer.
+
+## 5. Tests
+
+`llvm/test/Analysis/DDG/print-dot-function-ddg.ll` exercises the printer in both
+the default and `-dot-function-ddg-only` forms, covering register def-use edges
+and a memory dependence between a load and an aliasing store.
+
+Run it with `llvm-lit`:
+
+```bash
+llvm-lit -v llvm/test/Analysis/DDG/print-dot-function-ddg.ll
+```
+
+See also the `dot-function-ddg` entry in {doc}`Passes`.
diff --git a/llvm/docs/Passes.md b/llvm/docs/Passes.md
index 36159ff0c5206..86a0d1d9143aa 100644
--- a/llvm/docs/Passes.md
+++ b/llvm/docs/Passes.md
@@ -111,6 +111,18 @@ This pass, only available in `opt`, prints the dominator tree into a `.dot`
 graph, omitting the function bodies.  This graph can then be processed with the
 {program}`dot` tool to convert it to postscript or some other suitable format.
 
+### `dot-function-ddg`: Print function-scoped DDG to "dot" file
+
+This pass, only available in `opt`, prints a function-scoped Data Dependence
+Graph into a `.dot` graph (one file per function, named
+`<prefix>.<function>.dot` via `-dot-function-ddg-filename-prefix`).  It is the
+function-scope counterpart to the loop-scoped `dot-ddg` printer: it builds a
+Data Dependence Graph for the whole function and renders it with the same DDG
+graph traits, so it covers straight-line and cross-loop code that `dot-ddg`
+(which only handles loop nests) does not.  The `-dot-function-ddg-only` option
+selects the simplified rendering.  This graph can then be processed with the
+{program}`dot` tool to convert it to postscript or some other suitable format.
+
 ### `dot-post-dom`: Print postdominance tree of function to "dot" file
 
 This pass, only available in `opt`, prints the post dominator tree into a
diff --git a/llvm/docs/UserGuides.md b/llvm/docs/UserGuides.md
index 96024beae9fe1..e7b0b1051fe17 100644
--- a/llvm/docs/UserGuides.md
+++ b/llvm/docs/UserGuides.md
@@ -25,6 +25,7 @@ AdvancedBuilds
 WritingAnLLVMNewPMPass
 WritingAnLLVMPass
 Passes
+FunctionDDG
 StackSafetyAnalysis
 MergeFunctions
 AliasAnalysis
@@ -157,6 +158,11 @@ yaml2obj
 
   A list of optimizations and analyses implemented in LLVM.
 
+- {doc}`FunctionDDG`
+
+  Describes the function-scoped Data Dependence Graph DOT printer
+  (`dot-function-ddg`).
+
 - {doc}`StackSafetyAnalysis`
 
   This document describes the design of the stack safety analysis of local
diff --git a/llvm/include/llvm/Analysis/FunctionDDGPrinter.h b/llvm/include/llvm/Analysis/FunctionDDGPrinter.h
new file mode 100644
index 0000000000000..fbbbd99dd2792
--- /dev/null
+++ b/llvm/include/llvm/Analysis/FunctionDDGPrinter.h
@@ -0,0 +1,46 @@
+//===- llvm/Analysis/FunctionDDGPrinter.h ----------------------*- 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 a function-scoped DOT printer for the Data Dependence
+// Graph, the function-scope counterpart to the loop-scoped
+// llvm::DDGDotPrinterPass declared in DDGPrinter.h.
+//
+// Motivation: the loop-scoped DDG printer only covers code that has been
+// recognised as a loop nest. For visualising the dependence structure of
+// straight-line or cross-loop code -- for example SLP-style vector code or
+// fully unrolled kernels -- a function-wide view is required. This printer
+// builds a DataDependenceGraph for the whole function and renders it with the
+// existing DDG DOTGraphTraits, so loop- and function-scope graphs look alike.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_FUNCTIONDDGPRINTER_H
+#define LLVM_ANALYSIS_FUNCTIONDDGPRINTER_H
+
+#include "llvm/IR/PassManager.h"
+#include "llvm/Support/Compiler.h"
+
+namespace llvm {
+
+class Function;
+
+/// DOT printer pass for function-scoped data dependences.
+///
+/// Counterpart to DDGDotPrinterPass at function (rather than loop) scope. It
+/// builds a DataDependenceGraph for the whole function and writes it to a
+/// single DOT file named "<prefix>.<function-name>.dot", reusing the shared
+/// DDG DOTGraphTraits; -dot-function-ddg-only selects the simplified rendering.
+class FunctionDDGDotPrinterPass
+    : public PassInfoMixin<FunctionDDGDotPrinterPass> {
+public:
+  LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_ANALYSIS_FUNCTIONDDGPRINTER_H
diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt
index f3586c66cb056..b178c53383c89 100644
--- a/llvm/lib/Analysis/CMakeLists.txt
+++ b/llvm/lib/Analysis/CMakeLists.txt
@@ -65,6 +65,7 @@ add_llvm_component_library(LLVMAnalysis
   CycleAnalysis.cpp
   DDG.cpp
   DDGPrinter.cpp
+  FunctionDDGPrinter.cpp
   ConstraintSystem.cpp
   Delinearization.cpp
   DemandedBits.cpp
diff --git a/llvm/lib/Analysis/FunctionDDGPrinter.cpp b/llvm/lib/Analysis/FunctionDDGPrinter.cpp
new file mode 100644
index 0000000000000..27d11080ab594
--- /dev/null
+++ b/llvm/lib/Analysis/FunctionDDGPrinter.cpp
@@ -0,0 +1,58 @@
+//===- FunctionDDGPrinter.cpp - Function-scoped DDG DOT printer -----------===//
+//
+// 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 `-dot-function-ddg` pass, the function-scoped analogue
+// of the loop-scoped `-dot-ddg` printer. It builds a DataDependenceGraph for a
+// whole function and emits it in DOT format to a file named
+// `<prefix>.<function-name>.dot`, reusing the existing DDG DOTGraphTraits so
+// loop- and function-scope graphs render identically.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Analysis/FunctionDDGPrinter.h"
+#include "llvm/Analysis/DDG.h"
+#include "llvm/Analysis/DDGPrinter.h"
+#include "llvm/Analysis/DependenceAnalysis.h"
+#include "llvm/IR/Function.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/GraphWriter.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+
+static cl::opt<bool>
+    FunctionDDGDotOnly("dot-function-ddg-only", cl::Hidden,
+                       cl::desc("Print a simplified function-scoped DDG dot "
+                                "graph (like -dot-ddg-only at loop scope)."));
+
+static cl::opt<std::string> FunctionDDGDotFilenamePrefix(
+    "dot-function-ddg-filename-prefix", cl::init("ddg"), cl::Hidden,
+    cl::desc("The prefix used for the function DDG dot file names."));
+
+PreservedAnalyses FunctionDDGDotPrinterPass::run(Function &F,
+                                                 FunctionAnalysisManager &AM) {
+  auto &DI = AM.getResult<DependenceAnalysis>(F);
+  DataDependenceGraph G(F, DI);
+
+  std::string Filename =
+      (FunctionDDGDotFilenamePrefix + "." + F.getName() + ".dot").str();
+  errs() << "Writing '" << Filename << "'...";
+
+  std::error_code EC;
+  raw_fd_ostream File(Filename, EC, sys::fs::OF_Text);
+  if (!EC)
+    // Only the const DOTGraphTraits specialization is provided, hence the
+    // conversion to a const pointer.
+    WriteGraph(File, (const DataDependenceGraph *)&G, FunctionDDGDotOnly);
+  else
+    errs() << "  error opening file for writing!";
+  errs() << "\n";
+
+  return PreservedAnalyses::all();
+}
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 7f99f6376bcbf..68c7852269c8d 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -40,6 +40,7 @@
 #include "llvm/Analysis/DomPrinter.h"
 #include "llvm/Analysis/DominanceFrontier.h"
 #include "llvm/Analysis/EphemeralValuesCache.h"
+#include "llvm/Analysis/FunctionDDGPrinter.h"
 #include "llvm/Analysis/FunctionPropertiesAnalysis.h"
 #include "llvm/Analysis/GlobalsModRef.h"
 #include "llvm/Analysis/HashRecognize.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 04fa421cebb4b..758205668185a 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -440,6 +440,7 @@ FUNCTION_PASS("dfa-jump-threading", DFAJumpThreadingPass())
 FUNCTION_PASS("div-rem-pairs", DivRemPairsPass())
 FUNCTION_PASS("dot-cfg", CFGPrinterPass())
 FUNCTION_PASS("dot-cfg-only", CFGOnlyPrinterPass())
+FUNCTION_PASS("dot-function-ddg", FunctionDDGDotPrinterPass())
 FUNCTION_PASS("dot-dom", DomPrinter())
 FUNCTION_PASS("dot-dom-only", DomOnlyPrinter())
 FUNCTION_PASS("dot-post-dom", PostDomPrinter())
diff --git a/llvm/test/Analysis/DDG/print-dot-function-ddg.ll b/llvm/test/Analysis/DDG/print-dot-function-ddg.ll
new file mode 100644
index 0000000000000..e82d1d93c8b2b
--- /dev/null
+++ b/llvm/test/Analysis/DDG/print-dot-function-ddg.ll
@@ -0,0 +1,48 @@
+; The function-scoped DDG dot printer (-dot-function-ddg) is the whole-function
+; counterpart to the loop-scoped -dot-ddg printer: it builds a
+; DataDependenceGraph for the function and renders it with the shared DDG
+; DOTGraphTraits. The output therefore matches -dot-ddg -- register def-use
+; edges are blue and memory-dependence edges are red. -dot-function-ddg-only
+; selects the simplified rendering (concise labels, synthetic root hidden).
+
+; RUN: opt -aa-pipeline=basic-aa -passes=dot-function-ddg \
+; RUN:     -dot-function-ddg-filename-prefix=%t < %s -disable-output
+; RUN: FileCheck %s -input-file=%t.mem.dot
+
+; RUN: opt -aa-pipeline=basic-aa -passes=dot-function-ddg -dot-function-ddg-only \
+; RUN:     -dot-function-ddg-filename-prefix=%t.s < %s -disable-output
+; RUN: FileCheck %s -input-file=%t.s.mem.dot -check-prefix=SIMPLE
+
+;-----------------------------------------------------------------------------
+; @mem: load -> add -> store to the same pointer. The store depends on the add
+; (a register def-use) and on the load (a memory dependence). The verbose graph
+; shows the synthetic root; the simplified graph hides it.
+;-----------------------------------------------------------------------------
+; CHECK:      digraph "DDG for 'mem'"
+; The synthetic root and its rooted edges appear in the verbose graph:
+; CHECK-DAG:  [[ROOT:Node0x[0-9a-f]+]] [shape=record,label="{\<kind:root\>\nroot\n}"]
+; CHECK-DAG:  [[ROOT]] -> Node0x{{[0-9a-f]+}}[label="[rooted]"]
+; Register def-use edges are blue:
+; CHECK-DAG:  Node0x{{[0-9a-f]+}} -> Node0x{{[0-9a-f]+}}[label="[def-use]", color=blue]
+; The load/store memory dependence is a red edge:
+; CHECK-DAG:  Node0x{{[0-9a-f]+}} -> Node0x{{[0-9a-f]+}}[label="{{.*}}", color=red]
+; Node labels carry the instruction text:
+; CHECK-DAG:  label="{\<kind:single-instruction\>\n  %v = load i32, ptr %p, align 4\n}"
+; CHECK-DAG:  label="{\<kind:single-instruction\>\n  %s = add i32 %v, %v\n}"
+; CHECK-DAG:  label="{\<kind:single-instruction\>\n  store i32 %s, ptr %p, align 4\n}"
+
+; Simplified: root hidden, concise labels (no "<kind:>" prefix), memory edge
+; still present and labelled "[memory]".
+; SIMPLE:      digraph "DDG for 'mem'"
+; SIMPLE-DAG:  label="{  %v = load i32, ptr %p, align 4\n}"
+; SIMPLE-DAG:  Node0x{{[0-9a-f]+}} -> Node0x{{[0-9a-f]+}}[label="[def-use]", color=blue]
+; SIMPLE-DAG:  Node0x{{[0-9a-f]+}} -> Node0x{{[0-9a-f]+}}[label="[memory]", color=red]
+; SIMPLE-NOT:  kind:
+; SIMPLE-NOT:  [rooted]
+
+define void @mem(ptr %p) {
+  %v = load i32, ptr %p, align 4
+  %s = add i32 %v, %v
+  store i32 %s, ptr %p, align 4
+  ret void
+}



More information about the llvm-commits mailing list