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 namespace mlir::dataflow;
37
38namespace llzk {
39
40using namespace function;
41
42namespace dataflow {
43
44//===----------------------------------------------------------------------===//
45// Utilities
46//===----------------------------------------------------------------------===//
47
48void markAllOpsAsLive(mlir::DataFlowSolver &solver, mlir::Operation *top) {
49 for (mlir::Region &region : top->getRegions()) {
50 for (mlir::Block &block : region) {
51 (void)solver.getOrCreateState<mlir::dataflow::Executable>(&block)->setToLive();
52 for (mlir::Operation &oper : block) {
53 markAllOpsAsLive(solver, &oper);
54 }
55 }
56 }
57}
58
59//===----------------------------------------------------------------------===//
60// AbstractDenseForwardDataFlowAnalysis
61//===----------------------------------------------------------------------===//
62
64 // Visit every operation and block.
66 for (Region &region : top->getRegions()) {
67 for (Block &block : region) {
68 visitBlock(&block);
69 for (Operation &op : block) {
70 if (failed(initialize(&op))) {
71 return failure();
72 }
73 }
74 }
75 }
76 return success();
77}
78
79LogicalResult AbstractDenseForwardDataFlowAnalysis::visit(ProgramPoint point) {
80 if (auto *op = llvm::dyn_cast_if_present<Operation *>(point)) {
82 } else if (auto *block = llvm::dyn_cast_if_present<Block *>(point)) {
83 visitBlock(block);
84 } else {
85 return failure();
86 }
87 return success();
88}
89
92void AbstractDenseForwardDataFlowAnalysis::visitCallOperation(
93 CallOpInterface call, const AbstractDenseLattice &before, AbstractDenseLattice *after
94) {
95 // Allow for customizing the behavior of calls to external symbols, including
96 // when the analysis is explicitly marked as non-interprocedural.
97 auto callable = resolveCallable<FuncDefOp>(tables, call);
98 if (!getSolverConfig().isInterprocedural() ||
99 (mlir::succeeded(callable) && !callable->get().getCallableRegion())) {
100 return visitCallControlFlowTransfer(call, CallControlFlowAction::ExternalCallee, before, after);
101 }
102
105 SmallVector<Operation *> predecessors;
106 auto fnOp = callable.value().get();
107 fnOp.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(call.getOperation(), predecessor);
133 call, CallControlFlowAction::ExitCallee, *latticeAtCalleeReturn, latticeAfterCall
134 );
135 }
136}
137
139 // If the containing block is not executable, bail out.
140 if (!getOrCreateFor<Executable>(op, op->getBlock())->isLive()) {
141 return;
142 }
143
144 // Get the dense lattice to update.
145 AbstractDenseLattice *after = getLattice(op);
146
147 // Get the dense state before the execution of the op.
148 const AbstractDenseLattice *before;
149 if (Operation *prev = op->getPrevNode()) {
150 before = getLatticeFor(op, prev);
151 } else {
152 before = getLatticeFor(op, op->getBlock());
153 }
154
155 // If this op implements region control-flow, then control-flow dictates its
156 // transfer function.
157 if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
158 return visitRegionBranchOperation(op, branch, after);
159 }
160
161 // If this is a call operation, then join its lattices across known return
162 // sites.
163 if (auto call = dyn_cast<CallOpInterface>(op)) {
164 return visitCallOperation(call, *before, after);
165 }
166
167 // Invoke the operation transfer function.
168 visitOperationImpl(op, *before, after);
169}
170
173void AbstractDenseForwardDataFlowAnalysis::visitBlock(Block *block) {
174 // If the block is not executable, bail out.
175 if (!getOrCreateFor<Executable>(block, block)->isLive()) {
176 return;
177 }
178
179 // Get the dense lattice to update.
180 AbstractDenseLattice *after = getLattice(block);
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(mlir::succeeded(moduleOpRes), "could not get root module from callable");
194
195 SmallVector<Operation *> callsites;
196 moduleOpRes->walk([this, &callable, &callsites](CallOp call) mutable {
197 auto calledFnRes = resolveCallable<FuncDefOp>(tables, call);
198 if (mlir::succeeded(calledFnRes) &&
199 calledFnRes->get().getCallableRegion() == callable.getCallableRegion()) {
200 callsites.push_back(call);
201 }
202 });
203
204 for (Operation *callsite : callsites) {
205 // Get the dense lattice before the callsite.
206 const AbstractDenseLattice *before;
207 if (Operation *prev = callsite->getPrevNode()) {
208 before = getLatticeFor(block, prev);
209 } else {
210 before = getLatticeFor(block, callsite->getBlock());
211 }
212
214 llvm::cast<CallOpInterface>(callsite), CallControlFlowAction::EnterCallee, *before,
215 after
216 );
217 }
218 return;
219 }
220
221 // Check if we can reason about the control-flow.
222 if (auto branch = dyn_cast<RegionBranchOpInterface>(block->getParentOp())) {
223 return visitRegionBranchOperation(block, branch, after);
224 }
225
226 // Otherwise, we can't reason about the data-flow.
227 return setToEntryState(after);
228 }
229
230 // Join the state with the state after the block's predecessors.
231 for (Block::pred_iterator it = block->pred_begin(), e = block->pred_end(); it != e; ++it) {
232 // Skip control edges that aren't executable.
233 Block *predecessor = *it;
234 if (!getOrCreateFor<Executable>(block, getProgramPoint<CFGEdge>(predecessor, block))
235 ->isLive()) {
236 continue;
237 }
238
239 // Merge in the state from the predecessor's terminator.
240 join(after, *getLatticeFor(block, predecessor->getTerminator()));
241 }
242}
243
245 ProgramPoint point, RegionBranchOpInterface branch, AbstractDenseLattice *after
246) {
247 // Get the terminator predecessors.
248 const auto *predecessors = getOrCreateFor<PredecessorState>(point, point);
249 assert(predecessors->allPredecessorsKnown() && "unexpected unresolved region successors");
250
251 for (Operation *op : predecessors->getKnownPredecessors()) {
252 const AbstractDenseLattice *before;
253 // If the predecessor is the parent, get the state before the parent.
254 if (op == branch) {
255 if (Operation *prev = op->getPrevNode()) {
256 before = getLatticeFor(point, prev);
257 } else {
258 before = getLatticeFor(point, op->getBlock());
259 }
260
261 // Otherwise, get the state after the terminator.
262 } else {
263 before = getLatticeFor(point, op);
264 }
265
266 // This function is called in two cases:
267 // 1. when visiting the block (point = block);
268 // 2. when visiting the parent operation (point = parent op).
269 // In both cases, we are looking for predecessor operations of the point,
270 // 1. predecessor may be the terminator of another block from another
271 // region (assuming that the block does belong to another region via an
272 // assertion) or the parent (when parent can transfer control to this
273 // region);
274 // 2. predecessor may be the terminator of a block that exits the
275 // region (when region transfers control to the parent) or the operation
276 // before the parent.
277 // In the latter case, just perform the join as it isn't the control flow
278 // affected by the region.
279 std::optional<unsigned> regionFrom =
280 op == branch ? std::optional<unsigned>() : op->getBlock()->getParent()->getRegionNumber();
281 if (auto *toBlock = point.dyn_cast<Block *>()) {
282 unsigned regionTo = toBlock->getParent()->getRegionNumber();
283 visitRegionBranchControlFlowTransfer(branch, regionFrom, regionTo, *before, after);
284 } else {
285 assert(point.get<Operation *>() == branch && "expected to be visiting the branch itself");
286 // Only need to call the arc transfer when the predecessor is the region
287 // or the op itself, not the previous op.
288 if (op->getParentOp() == branch || op == branch) {
290 branch, regionFrom, /*regionTo=*/std::nullopt, *before, after
291 );
292 } else {
293 join(after, *before);
294 }
295 }
296 }
297}
298
300AbstractDenseForwardDataFlowAnalysis::getLatticeFor(ProgramPoint dependent, ProgramPoint point) {
301 AbstractDenseLattice *state = getLattice(point);
302 addDependency(state, dependent);
303 return state;
304}
305
306} // namespace dataflow
307
308} // namespace llzk
This file implements (LLZK-tailored) dense data-flow analysis using the data-flow analysis framework.
virtual void 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 visitRegionBranchOperation(mlir::ProgramPoint point, mlir::RegionBranchOpInterface branch, AbstractDenseLattice *after)
Visit a program point within a region branch operation with predecessors in it.
void join(AbstractDenseLattice *lhs, const AbstractDenseLattice &rhs)
Join a lattice with another and propagate an update if it changed.
virtual void visitCallControlFlowTransfer(mlir::CallOpInterface call, CallControlFlowAction action, const AbstractDenseLattice &before, AbstractDenseLattice *after)
Propagate the dense lattice forward along the call control flow edge, which can be either entering or...
const AbstractDenseLattice * getLatticeFor(mlir::ProgramPoint dependent, mlir::ProgramPoint point)
Get the dense lattice after the execution of the given program point and add it as a dependency to a ...
mlir::SymbolTableCollection tables
LLZK: Added for use of symbol helper caching.
virtual void processOperation(mlir::Operation *op)
Visit an operation.
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;...
mlir::LogicalResult visit(mlir::ProgramPoint point) override
Visit a program point that modifies the state of the program.
virtual AbstractDenseLattice * getLattice(mlir::ProgramPoint point)=0
Get the dense lattice after the execution of the given program point.
virtual void visitRegionBranchControlFlowTransfer(mlir::RegionBranchOpInterface branch, 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...
void markAllOpsAsLive(mlir::DataFlowSolver &solver, mlir::Operation *top)
LLZK: Added this utility to ensure analysis is performed for all structs in a given module.
mlir::dataflow::AbstractDenseLattice AbstractDenseLattice
void ensure(bool condition, llvm::Twine errMsg)
Definition ErrorHelper.h:32
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)