Enzyme main
Loading...
Searching...
No Matches
DataFlowActivityAnalysis.h
Go to the documentation of this file.
1//===- DataFlowActivityAnalysis.h - Declaration of Activity Analysis ------===//
2//
3// Enzyme Project
4//
5// Part of the Enzyme Project, under the Apache License v2.0 with LLVM
6// Exceptions. See https://llvm.org/LICENSE.txt for license information.
7// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8//
9// If using this code in an academic setting, please cite the following:
10// @incollection{enzymeNeurips,
11// title = {Instead of Rewriting Foreign Code for Machine Learning,
12// Automatically Synthesize Fast Gradients},
13// author = {Moses, William S. and Churavy, Valentin},
14// booktitle = {Advances in Neural Information Processing Systems 33},
15// year = {2020},
16// note = {To appear in},
17// }
18//
19//===----------------------------------------------------------------------===//
20//
21// This file contains the declaration of Activity Analysis -- an AD-specific
22// analysis that deduces if a given instruction or value can impact the
23// calculation of a derivative. This file formulates activity analysis within
24// a dataflow framework.
25//
26//===----------------------------------------------------------------------===//
27#ifndef ENZYME_MLIR_ANALYSIS_DATAFLOW_ACTIVITYANALYSIS_H
28#define ENZYME_MLIR_ANALYSIS_DATAFLOW_ACTIVITYANALYSIS_H
29
30#include "mlir/Analysis/DataFlow/DenseAnalysis.h"
31#include "mlir/Analysis/DataFlow/SparseAnalysis.h"
32#include "mlir/Analysis/DataFlowFramework.h"
33#include "mlir/IR/Block.h"
34#include "mlir/Interfaces/FunctionInterfaces.h"
35#include "mlir/Interfaces/SideEffectInterfaces.h"
36#include "mlir/Support/LLVM.h"
37
39#include "Dialect/Ops.h"
40
42
43using namespace mlir::dataflow;
44namespace mlir {
45class FunctionOpInterface;
46
47namespace enzyme {
48
49enum class Activity : uint32_t;
50
51/// From LLVM Enzyme's activity analysis, there are four activity states.
52// constant instruction vs constant value, a value/instruction (one and the same
53// in LLVM) can be a constant instruction but active value, active instruction
54// but constant value, or active/constant both.
55
56// The result of activity states are potentially different for multiple
57// enzyme.autodiff calls.
59
61public:
65
69
73
74 bool isActiveVal() const { return value == ActivityKind::ActiveVal; }
75
76 bool isConstant() const { return value == ActivityKind::Constant; }
77
78 bool isUnknown() const { return value == ActivityKind::Unknown; }
79
81 ValueActivity(ActivityKind value) : value(value) {}
82
83 /// Get the known activity state.
84 const ActivityKind &getValue() const { return value; }
85
86 bool operator==(const ValueActivity &rhs) const { return value == rhs.value; }
87
89 const ValueActivity &rhs) {
90 if (lhs.isUnknown() || rhs.isUnknown())
92
93 if (lhs.isConstant() && rhs.isConstant())
96 }
97
98 static ValueActivity join(const ValueActivity &lhs,
99 const ValueActivity &rhs) {
100 return ValueActivity::merge(lhs, rhs);
101 }
102
103 void print(raw_ostream &os) const;
104 raw_ostream &operator<<(raw_ostream &os) const;
105
106private:
107 /// The activity kind. Optimistically initialized to constant.
109};
110
111//===----------------------------------------------------------------------===//
112// ForwardValueActivity
113//===----------------------------------------------------------------------===//
114class ForwardValueActivity : public Lattice<enzyme::ValueActivity> {
115public:
116 using Lattice::Lattice;
117};
118
119//===----------------------------------------------------------------------===//
120// BackwardValueActivity
121//===----------------------------------------------------------------------===//
123public:
124 using AbstractSparseLattice::AbstractSparseLattice;
125
126 enzyme::ValueActivity getValue() const { return value; }
127
128 void print(raw_ostream &os) const override;
129
130 ChangeResult meet(const AbstractSparseLattice &other) override;
131
132 ChangeResult meet(enzyme::ValueActivity other);
133
134private:
136};
137
138/// Sparse activity analysis reasons about activity by traversing forward down
139/// the def-use chains starting from active function arguments.
141 : public SparseForwardDataFlowAnalysis<enzyme::ForwardValueActivity> {
142public:
143 using SparseForwardDataFlowAnalysis::SparseForwardDataFlowAnalysis;
144
145 void setToEntryState(enzyme::ForwardValueActivity *lattice) override;
146
147 LogicalResult
148 visitOperation(Operation *op,
149 ArrayRef<const enzyme::ForwardValueActivity *> operands,
150 ArrayRef<enzyme::ForwardValueActivity *> results) override;
151
152 void
153 visitExternalCall(CallOpInterface call,
154 ArrayRef<const enzyme::ForwardValueActivity *> operands,
155 ArrayRef<enzyme::ForwardValueActivity *> results) override;
156
157 void transfer(Operation *op,
158 ArrayRef<const enzyme::ForwardValueActivity *> operands,
159 ArrayRef<enzyme::ForwardValueActivity *> results);
160};
161
163 : public SparseBackwardDataFlowAnalysis<enzyme::BackwardValueActivity> {
164public:
165 using SparseBackwardDataFlowAnalysis::SparseBackwardDataFlowAnalysis;
166
168 // llvm::errs() << "backward sparse setting to exit state\n";
169 }
170
171 void visitBranchOperand(OpOperand &operand) override {}
172
173 void visitCallOperand(OpOperand &operand) override {}
174
175 void
176 visitNonControlFlowArguments(RegionSuccessor &successor,
177 ArrayRef<BlockArgument> arguments) override {}
178
179 void transfer(Operation *op,
180 ArrayRef<enzyme::BackwardValueActivity *> operands,
181 ArrayRef<const enzyme::BackwardValueActivity *> results);
182
183 LogicalResult visitOperation(
184 Operation *op, ArrayRef<enzyme::BackwardValueActivity *> operands,
185 ArrayRef<const enzyme::BackwardValueActivity *> results) override;
186
188 CallOpInterface call, ArrayRef<enzyme::BackwardValueActivity *> operands,
189 ArrayRef<const enzyme::BackwardValueActivity *> results) override;
190};
191
192/// This needs to keep track of three things:
193/// 1. Could active info store in?
194/// 2. Could active info load out?
195/// TODO: Necessary for run-time activity
196/// 3. Could constant info propagate (store?) in?
197///
198/// Active: (forward) active in && (backward) active out && (??) !const in
199/// ActiveOrConstant: active in && active out && const in
200/// Constant: everything else
202 /// Whether active data has stored into this memory location.
203 bool activeIn = false;
204 /// Whether active data was loaded out of this memory location.
205 bool activeOut = false;
206
207 bool operator==(const MemoryActivityState &other) {
208 return activeIn == other.activeIn && activeOut == other.activeOut;
209 }
210
211 bool operator!=(const MemoryActivityState &other) {
212 return !(*this == other);
213 }
214
215 ChangeResult reset();
216 ChangeResult merge(const MemoryActivityState &other);
217};
218
220public:
221 using AbstractDenseLattice::AbstractDenseLattice;
222
223 /// Clear all modifications.
224 ChangeResult reset();
225
226 bool hasActiveData(DistinctAttr aliasClass) const;
227
228 bool activeDataFlowsOut(DistinctAttr aliasClass) const;
229
230 /// Set the internal activity state. Accepts null attribute to indicate "other
231 /// classes".
232 ChangeResult setActiveIn(DistinctAttr aliasClass);
233 ChangeResult setActiveIn();
234 ChangeResult setActiveOut(DistinctAttr aliasClass);
235 ChangeResult setActiveOut();
236 void print(raw_ostream &os) const override;
237 raw_ostream &operator<<(raw_ostream &os) const;
238
239protected:
240 ChangeResult merge(const AbstractDenseLattice &lattice);
241
242private:
243 DenseMap<DistinctAttr, MemoryActivityState> activityStates;
244 MemoryActivityState otherMemoryActivity;
245};
246
248public:
249 using MemoryActivity::MemoryActivity;
250
251 /// Join the activity states.
252 ChangeResult join(const AbstractDenseLattice &lattice) {
253 return merge(lattice);
254 }
255};
256
258public:
259 using MemoryActivity::MemoryActivity;
260
261 ChangeResult meet(const AbstractDenseLattice &lattice) override {
262 return merge(lattice);
263 }
264};
265
267 : public DenseForwardDataFlowAnalysis<ForwardMemoryActivity> {
268public:
269 DenseForwardActivityAnalysis(DataFlowSolver &solver, Block *entryBlock,
270 ArrayRef<enzyme::Activity> argumentActivity)
271 : DenseForwardDataFlowAnalysis(solver), entryBlock(entryBlock),
272 argumentActivity(argumentActivity) {}
273
274 LogicalResult visitOperation(Operation *op,
275 const ForwardMemoryActivity &before,
276 ForwardMemoryActivity *after) override;
277
278 void visitCallControlFlowTransfer(CallOpInterface call,
279 CallControlFlowAction action,
280 const ForwardMemoryActivity &before,
281 ForwardMemoryActivity *after) override {
282 join(after, before);
283 }
284
285 /// Initialize the entry block with the supplied argument activities.
286 void setToEntryState(ForwardMemoryActivity *lattice) override;
287
288private:
289 // A pointer to the entry block and argument activities of the top-level
290 // function being differentiated. This is used to set the entry state
291 // because we need access to the results of points-to analysis.
292 Block *entryBlock;
293 SmallVector<enzyme::Activity> argumentActivity;
294};
295
297 : public DenseBackwardDataFlowAnalysis<BackwardMemoryActivity> {
298public:
299 DenseBackwardActivityAnalysis(DataFlowSolver &solver,
300 SymbolTableCollection &symbolTable,
301 FunctionOpInterface parentOp,
302 ArrayRef<enzyme::Activity> argumentActivity,
303 ArrayRef<enzyme::Activity> returnActivity)
304 : DenseBackwardDataFlowAnalysis(solver, symbolTable), parentOp(parentOp),
305 argumentActivity(argumentActivity), returnActivity(returnActivity) {}
306
307 LogicalResult visitOperation(Operation *op,
308 const BackwardMemoryActivity &after,
309 BackwardMemoryActivity *before) override;
310
311 void visitCallControlFlowTransfer(CallOpInterface call,
312 CallControlFlowAction action,
313 const BackwardMemoryActivity &after,
314 BackwardMemoryActivity *before) override {
315 meet(before, after);
316 }
317
318 void setToExitState(BackwardMemoryActivity *lattice) override {}
319
320private:
321 FunctionOpInterface parentOp;
322 SmallVector<enzyme::Activity> argumentActivity;
323 SmallVector<enzyme::Activity> returnActivity;
324};
325
326void runDataFlowActivityAnalysis(FunctionOpInterface callee,
327 ArrayRef<enzyme::Activity> argumentActivity,
328 ArrayRef<enzyme::Activity> returnActivity,
329 bool print = false, bool verbose = false,
330 bool annotate = false);
331
332} // namespace enzyme
333} // namespace mlir
334
335#endif
ChangeResult meet(const AbstractDenseLattice &lattice) override
ChangeResult meet(const AbstractSparseLattice &other) override
void print(raw_ostream &os) const override
void visitCallControlFlowTransfer(CallOpInterface call, CallControlFlowAction action, const BackwardMemoryActivity &after, BackwardMemoryActivity *before) override
DenseBackwardActivityAnalysis(DataFlowSolver &solver, SymbolTableCollection &symbolTable, FunctionOpInterface parentOp, ArrayRef< enzyme::Activity > argumentActivity, ArrayRef< enzyme::Activity > returnActivity)
LogicalResult visitOperation(Operation *op, const BackwardMemoryActivity &after, BackwardMemoryActivity *before) override
void setToExitState(BackwardMemoryActivity *lattice) override
LogicalResult visitOperation(Operation *op, const ForwardMemoryActivity &before, ForwardMemoryActivity *after) override
DenseForwardActivityAnalysis(DataFlowSolver &solver, Block *entryBlock, ArrayRef< enzyme::Activity > argumentActivity)
void setToEntryState(ForwardMemoryActivity *lattice) override
Initialize the entry block with the supplied argument activities.
void visitCallControlFlowTransfer(CallOpInterface call, CallControlFlowAction action, const ForwardMemoryActivity &before, ForwardMemoryActivity *after) override
ChangeResult join(const AbstractDenseLattice &lattice)
Join the activity states.
void print(raw_ostream &os) const override
bool activeDataFlowsOut(DistinctAttr aliasClass) const
ChangeResult reset()
Clear all modifications.
ChangeResult merge(const AbstractDenseLattice &lattice)
raw_ostream & operator<<(raw_ostream &os) const
bool hasActiveData(DistinctAttr aliasClass) const
void visitExternalCall(CallOpInterface call, ArrayRef< enzyme::BackwardValueActivity * > operands, ArrayRef< const enzyme::BackwardValueActivity * > results) override
void setToExitState(enzyme::BackwardValueActivity *lattice) override
void transfer(Operation *op, ArrayRef< enzyme::BackwardValueActivity * > operands, ArrayRef< const enzyme::BackwardValueActivity * > results)
void visitBranchOperand(OpOperand &operand) override
LogicalResult visitOperation(Operation *op, ArrayRef< enzyme::BackwardValueActivity * > operands, ArrayRef< const enzyme::BackwardValueActivity * > results) override
void visitNonControlFlowArguments(RegionSuccessor &successor, ArrayRef< BlockArgument > arguments) override
Sparse activity analysis reasons about activity by traversing forward down the def-use chains startin...
void transfer(Operation *op, ArrayRef< const enzyme::ForwardValueActivity * > operands, ArrayRef< enzyme::ForwardValueActivity * > results)
void setToEntryState(enzyme::ForwardValueActivity *lattice) override
In general, we don't know anything about entry operands.
void visitExternalCall(CallOpInterface call, ArrayRef< const enzyme::ForwardValueActivity * > operands, ArrayRef< enzyme::ForwardValueActivity * > results) override
LogicalResult visitOperation(Operation *op, ArrayRef< const enzyme::ForwardValueActivity * > operands, ArrayRef< enzyme::ForwardValueActivity * > results) override
static ValueActivity join(const ValueActivity &lhs, const ValueActivity &rhs)
void print(raw_ostream &os) const
static ValueActivity merge(const ValueActivity &lhs, const ValueActivity &rhs)
const ActivityKind & getValue() const
Get the known activity state.
bool operator==(const ValueActivity &rhs) const
raw_ostream & operator<<(raw_ostream &os) const
@ Activity
Names the activity of the argument that follows it.
ActivityKind
From LLVM Enzyme's activity analysis, there are four activity states.
void runDataFlowActivityAnalysis(FunctionOpInterface callee, ArrayRef< enzyme::Activity > argumentActivity, ArrayRef< enzyme::Activity > returnActivity, bool print=false, bool verbose=false, bool annotate=false)
This needs to keep track of three things:
bool activeIn
Whether active data has stored into this memory location.
ChangeResult merge(const MemoryActivityState &other)
bool operator==(const MemoryActivityState &other)
bool operator!=(const MemoryActivityState &other)
bool activeOut
Whether active data was loaded out of this memory location.