LLZK 0.1.0
Veridise's ZK Language IR
Loading...
Searching...
No Matches
DenseAnalysis.cpp
Go to the documentation of this file.
1//===- DenseAnalysis.cpp - Dense data-flow analysis -------------*- C++ -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8// Adapted from mlir/lib/Analysis/DataFlow/DenseAnalysis.cpp
9// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
10// See https://llvm.org/LICENSE.txt for license information.
11// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
12//
13//===----------------------------------------------------------------------===//
14
19
20#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
21#include <mlir/Analysis/DataFlowFramework.h>
22#include <mlir/IR/Block.h>
23#include <mlir/IR/Operation.h>
24#include <mlir/IR/Region.h>
25#include <mlir/Interfaces/CallInterfaces.h>
26#include <mlir/Interfaces/ControlFlowInterfaces.h>
27#include <mlir/Support/LLVM.h>
28#include <mlir/Support/LogicalResult.h>
29
30#include <llvm/ADT/STLExtras.h>
31#include <llvm/Support/Casting.h>
32
33#include <optional>
34
35using namespace mlir;
36using Executable = mlir::dataflow::Executable;
37using CFGEdge = mlir::dataflow::CFGEdge;
38
39namespace llzk {
40
41using namespace function;
42
43namespace dataflow {
44
45//===----------------------------------------------------------------------===//
46// Utilities
47//===----------------------------------------------------------------------===//
48
49void markAllOpsAsLive(DataFlowSolver &solver, Operation *top) {
50 for (Region &region : top->getRegions()) {
51 for (Block &block : region) {
52 ProgramPoint *point = solver.getProgramPointBefore(&block);
53 (void)solver.getOrCreateState<Executable>(point)->setToLive();
54 for (Operation &oper : block) {
55 markAllOpsAsLive(solver, &oper);
56 }
57 }
58 }
59}
60
61//===----------------------------------------------------------------------===//
62// AbstractDenseForwardDataFlowAnalysis
63//===----------------------------------------------------------------------===//
64
66 // Visit every operation and block.
67 if (failed(processOperation(top))) {
68 return failure();
69 }
70 for (Region &region : top->getRegions()) {
71 for (Block &block : region) {
72 visitBlock(&block);
73 for (Operation &op : block) {
74 if (failed(initialize(&op))) {
75 return failure();
76 }
77 }
78 }
79 }
80 return success();
81}
82
83LogicalResult AbstractDenseForwardDataFlowAnalysis::visit(ProgramPoint *point) {
84 if (!point->isBlockStart()) {
85 return processOperation(point->getPrevOp());
86 }
87 visitBlock(point->getBlock());
88 return success();
89}
90
93void AbstractDenseForwardDataFlowAnalysis::visitCallOperation(
94 CallOpInterface call, const AbstractDenseLattice &before, AbstractDenseLattice *after
95) {
96 // Allow for customizing the behavior of calls to external symbols, including
97 // when the analysis is explicitly marked as non-interprocedural.
98 auto callable = resolveCallable<FuncDefOp>(tables, call);
99 if (!getSolverConfig().isInterprocedural() ||
100 (succeeded(callable) && !callable->get().getCallableRegion())) {
101 return visitCallControlFlowTransfer(call, CallControlFlowAction::ExternalCallee, before, after);
102 }
103
106 SmallVector<Operation *> predecessors;
107 callable->get().walk([&predecessors](ReturnOp ret) mutable { predecessors.push_back(ret); });
108
109 // If we have no predecessors, we cannot reason about dataflow, since there is
110 // no return value.
111 if (predecessors.empty()) {
112 return setToEntryState(after);
113 }
114
115 for (Operation *predecessor : predecessors) {
116 // Get the lattices at callee return:
117 //
118 // function.def @callee() {
119 // ...
120 // return // predecessor
121 // // latticeAtCalleeReturn
122 // }
123 // function.def @caller() {
124 // ...
125 // call @callee
126 // // latticeAfterCall
127 // ...
128 // }
129 AbstractDenseLattice *latticeAfterCall = after;
130 const AbstractDenseLattice *latticeAtCalleeReturn =
131 getLatticeFor(getProgramPointAfter(call.getOperation()), getProgramPointAfter(predecessor));
133 call, CallControlFlowAction::ExitCallee, *latticeAtCalleeReturn, latticeAfterCall
134 );
135 }
136}
137
139 ProgramPoint *point = getProgramPointAfter(op);
140 // If the containing block is not executable, bail out.
141 if (op->getBlock() != nullptr &&
142 !getOrCreateFor<Executable>(point, getProgramPointBefore(op->getBlock()))->isLive()) {
143 return success();
144 }
145
146 // Get the dense lattice to update.
147 AbstractDenseLattice *after = getLattice(point);
148
149 // Get the dense state before the execution of the op.
150 const AbstractDenseLattice *before = getLatticeFor(point, getProgramPointBefore(op));
151
152 // If this op implements region control-flow, then control-flow dictates its
153 // transfer function.
154 if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
155 visitRegionBranchOperation(point, branch, after);
156 return success();
157 }
158
159 // If this is a call operation, then join its lattices across known return
160 // sites.
161 if (auto call = dyn_cast<CallOpInterface>(op)) {
162 visitCallOperation(call, *before, after);
163 return success();
164 }
165
166 // Invoke the operation transfer function.
167 return visitOperationImpl(op, *before, after);
168}
169
172void AbstractDenseForwardDataFlowAnalysis::visitBlock(Block *block) {
173 // If the block is not executable, bail out.
174 ProgramPoint *point = getProgramPointBefore(block);
175 if (!getOrCreateFor<Executable>(point, point)->isLive()) {
176 return;
177 }
178
179 // Get the dense lattice to update.
180 AbstractDenseLattice *after = getLattice(point);
181
182 // The dense lattices of entry blocks are set by region control-flow or the
183 // callgraph.
184 if (block->isEntryBlock()) {
185 // Check if this block is the entry block of a callable region.
186 auto callable = dyn_cast<CallableOpInterface>(block->getParentOp());
187 if (callable && callable.getCallableRegion() == block->getParent()) {
188 if (!getSolverConfig().isInterprocedural()) {
189 return setToEntryState(after);
190 }
192 auto moduleOpRes = getTopRootModule(callable.getOperation());
193 ensure(succeeded(moduleOpRes), "could not get root module from callable");
194 SmallVector<Operation *> callsites;
195 moduleOpRes->walk([this, &callable, &callsites](CallOp call) mutable {
196 auto calledFnRes = resolveCallable<FuncDefOp>(tables, call);
197 if (succeeded(calledFnRes) &&
198 calledFnRes->get().getCallableRegion() == callable.getCallableRegion()) {
199 callsites.push_back(call);
200 }
201 });
202
203 for (Operation *callsite : callsites) {
204 // Get the dense lattice before the callsite.
205 const AbstractDenseLattice *before = getLatticeFor(point, getProgramPointBefore(callsite));
206
208 llvm::cast<CallOpInterface>(callsite), CallControlFlowAction::EnterCallee, *before,
209 after
210 );
211 }
212 return;
213 }
214
215 // Check if we can reason about the control-flow.
216 if (auto branch = dyn_cast<RegionBranchOpInterface>(block->getParentOp())) {
217 return visitRegionBranchOperation(point, branch, after);
218 }
219
220 // Otherwise, we can't reason about the data-flow.
221 return setToEntryState(after);
222 }
223
224 // Join the state with the state after the block's predecessors.
225 for (Block::pred_iterator it = block->pred_begin(), e = block->pred_end(); it != e; ++it) {
226 // Skip control edges that aren't executable.
227 Block *predecessor = *it;
228 if (!getOrCreateFor<Executable>(point, getLatticeAnchor<CFGEdge>(predecessor, block))
229 ->isLive()) {
230 continue;
231 }
232
233 // Merge in the state from the predecessor's terminator.
234 join(after, *getLatticeFor(point, getProgramPointAfter(predecessor->getTerminator())));
235 }
236}
237
240 ProgramPoint *point, RegionBranchOpInterface branch, AbstractDenseLattice *after
241) {
242 Operation *op = point->isBlockStart() ? point->getBlock()->getParentOp() : point->getPrevOp();
243 if (op) {
244 const AbstractDenseLattice *before;
245 // If the predecessor is the parent, get the state before the parent.
246 if (op == branch) {
247 before = getLatticeFor(point, getProgramPointBefore(op));
248 // Otherwise, get the state after the terminator.
249 } else {
250 before = getLatticeFor(point, getProgramPointAfter(op));
251 }
252
253 // This function is called in two cases:
254 // 1. when visiting the block (point = block start);
255 // 2. when visiting the parent operation (point = iter after parent op).
256 // In both cases, we are looking for predecessor operations of the point,
257 // 1. predecessor may be the terminator of another block from another
258 // region (assuming that the block does belong to another region via an
259 // assertion) or the parent (when parent can transfer control to this
260 // region);
261 // 2. predecessor may be the terminator of a block that exits the
262 // region (when region transfers control to the parent) or the operation
263 // before the parent.
264 // In the latter case, just perform the join as it isn't the control flow
265 // affected by the region.
266 std::optional<unsigned> regionFrom =
267 op == branch ? std::optional<unsigned>() : op->getBlock()->getParent()->getRegionNumber();
268 if (point->isBlockStart()) {
269 unsigned regionTo = point->getBlock()->getParent()->getRegionNumber();
270 visitRegionBranchControlFlowTransfer(branch, regionFrom, regionTo, *before, after);
271 } else {
272 assert(point->getPrevOp() == branch && "expected to be visiting the branch itself");
273 // Only need to call the arc transfer when the predecessor is the region
274 // or the op itself, not the previous op.
275 if (op->getParentOp() == branch || op == branch) {
277 branch, regionFrom, /*regionTo=*/std::nullopt, *before, after
278 );
279 } else {
280 join(after, *before);
281 }
282 }
283 }
284}
285
287AbstractDenseForwardDataFlowAnalysis::getLatticeFor(ProgramPoint *dependent, LatticeAnchor anchor) {
288 AbstractDenseLattice *state = getLattice(anchor);
289 addDependency(state, dependent);
290 return state;
291}
292
293} // namespace dataflow
294
295} // namespace llzk
mlir::dataflow::Executable Executable
mlir::dataflow::CFGEdge CFGEdge
This file implements (LLZK-tailored) dense data-flow analysis using the data-flow analysis framework.
void join(AbstractDenseLattice *lhs, const AbstractDenseLattice &rhs)
Join a lattice with another and propagate an update if it changed.
virtual void visitCallControlFlowTransfer(mlir::CallOpInterface, CallControlFlowAction action, const AbstractDenseLattice &before, AbstractDenseLattice *after)
Propagate the dense lattice forward along the call control flow edge, which can be either entering or...
virtual mlir::LogicalResult processOperation(mlir::Operation *op)
Visit an operation.
virtual AbstractDenseLattice * getLattice(mlir::LatticeAnchor anchor)=0
Get the dense lattice on the given lattice anchor.
mlir::LogicalResult visit(mlir::ProgramPoint *point) override
Visit a program point that modifies the state of the program.
void visitRegionBranchOperation(mlir::ProgramPoint *point, mlir::RegionBranchOpInterface branch, AbstractDenseLattice *after)
Visit a program point within a region branch operation with predecessors in it.
mlir::SymbolTableCollection tables
LLZK: Added for use of symbol helper caching.
virtual void setToEntryState(AbstractDenseLattice *lattice)=0
Set the dense lattice at control flow entry point and propagate an update if it changed.
mlir::LogicalResult initialize(mlir::Operation *top) override
Initialize the analysis by visiting every program point whose execution may modify the program state;...
virtual void visitRegionBranchControlFlowTransfer(mlir::RegionBranchOpInterface, std::optional< unsigned > regionFrom, std::optional< unsigned > regionTo, const AbstractDenseLattice &before, AbstractDenseLattice *after)
Propagate the dense lattice forward along the control flow edge from regionFrom to regionTo regions o...
const AbstractDenseLattice * getLatticeFor(mlir::ProgramPoint *dependent, mlir::LatticeAnchor anchor)
Get the dense lattice on the given lattice anchor and add dependent as its dependency.
virtual mlir::LogicalResult visitOperationImpl(mlir::Operation *op, const AbstractDenseLattice &before, AbstractDenseLattice *after)=0
Propagate the dense lattice before the execution of an operation to the lattice after its execution.
void markAllOpsAsLive(DataFlowSolver &solver, Operation *top)
mlir::dataflow::AbstractDenseLattice AbstractDenseLattice
void ensure(bool condition, const llvm::Twine &errMsg)
mlir::FailureOr< SymbolLookupResult< T > > resolveCallable(mlir::SymbolTableCollection &symbolTable, mlir::CallOpInterface call)
Based on mlir::CallOpInterface::resolveCallable, but using LLZK lookup helpers.
FailureOr< ModuleOp > getTopRootModule(Operation *from)