Skip to main content

Analyses

An analysis reads a Structured SDFG and computes facts about it: which loops exist, which containers are read and written where, whether iterations of a loop depend on each other. Analyses never change the SDFG. Passes and transformations use them to decide what they can change.

The analysis manager​

Analyses are not run eagerly. An AnalysisManager creates an analysis the first time it is requested, caches the result, and returns the cached result on later requests:

analysis::AnalysisManager analysis_manager(builder.subject());
auto& loops = analysis_manager.get<analysis::LoopAnalysis>(); // computed now
auto& again = analysis_manager.get<analysis::LoopAnalysis>(); // cached

When a pass or transformation changes the SDFG, cached analyses become outdated. Passes report which analyses they invalidate (Pass::invalidates), and the manager either asks each analysis to update itself for the changed subtree (Analysis::invalidate) or discards it, so it is recomputed on the next request. In Python, create a new AnalysisManager after a change, or call invalidate_all().

Example: the WAXPBY loop​

Using build_waxpby() from Structured SDFGs:

from waxpby_sdfg import build_waxpby
from docc.sdfg import AnalysisManager

builder = build_waxpby()
analysis_manager = AnalysisManager(builder)

loops = analysis_manager.loop_analysis()
loop = loops.find_loop_by_indvar("i")
info = loops.loop_info(loop)

print(loop) # <For indvar='i' init=0 condition=i < N id=1>
print(info.num_loops) # 1
print(info.is_perfectly_nested) # True
print(info.is_perfectly_parallel) # False: the loop is a For, not yet a Map

is_perfectly_parallel describes the structure: it is True if the loop nest consists only of Maps. Whether the For could become a Map is a different question, answered by the dependency analyses below. They show that WAXPBY's iterations are independent, which is why LoopParallelization can turn it into a Map.

Available analyses​

AnalysisLocationComputes
LoopAnalysissdfg/The loop tree: all loops, their nesting, outermost loops, and per-loop facts (LoopInfo)
Userssdfg/Every read, write, and view of every container, with the control flow node where it happens
DataDependencyAnalysissdfg/Which writes each read can observe (read-after-write relations)
LoopCarriedDependencyAnalysisopt/Dependencies between iterations of a loop; decides whether a loop can be parallelized or is a reduction
MemoryLayoutAnalysissdfg/The memory layout (shape and strides) behind each memlet, for example for linearized arrays
AssumptionsAnalysissdfg/Known bounds of symbols at each point, for example 0 <= i < N inside the loop
TypeAnalysissdfg/The element types of containers, including pointers whose type is only known from their use
ArgumentsAnalysissdfg/How each argument of the function is used: read, written, or both
ControlFlowAnalysissdfg/Dominance and order between control flow nodes
ReferenceAnalysissdfg/Pointers that alias other containers
TileAnalysis, ReductionBufferAnalysisopt/Tiles and reduction buffers in loop nests, used by tiling and GPU code generation
DataTransferEliminationAnalysisopt/Which host-device transfers are redundant

The headers are in sdfg/include/sdfg/analysis/ and opt/include/sdfg/*/analysis/; see the C++ API reference. The Python bindings expose loop_analysis(), users(), assumptions_analysis(), type_analysis(), arguments_analysis(), control_flow_analysis(), and tile_analysis().

How analyses build on each other​

Analyses request other analyses through the manager. Deciding whether a loop can be parallelized, for example, combines several of them:

LoopParallelization.can_be_applied
└── AutoParallelization::classify → None | Map | Reduce
├── LoopAnalysis which loop, and what is nested inside
├── Users which containers the loop body reads and writes
├── MemoryLayoutAnalysis which elements each access touches
└── LoopCarriedDependencyAnalysis do two iterations touch the same element?
└── AssumptionsAnalysis bounds of the induction variable

Write an analysis​

An analysis is a C++ class that derives from analysis::Analysis and implements run. It can request other analyses from the manager:

#include "sdfg/analysis/analysis.h"
#include "sdfg/analysis/loop_analysis.h"

class CountMapsAnalysis : public sdfg::analysis::Analysis {
size_t num_maps_ = 0;

protected:
void run(sdfg::analysis::AnalysisManager& analysis_manager) override {
auto& loops = analysis_manager.get<sdfg::analysis::LoopAnalysis>();
for (auto* loop : loops.loops()) {
if (dynamic_cast<sdfg::structured_control_flow::Map*>(loop)) num_maps_++;
}
}

public:
using Analysis::Analysis;
std::string name() const override { return "CountMapsAnalysis"; }
size_t num_maps() const { return num_maps_; }
};

Override invalidate(ControlFlowNode& changed) and return true if the analysis can update itself after a local change instead of being recomputed.

Continue with Passes to see how docc changes the SDFG.